Create view structures

pull/42/head
Miguel Mota 5 years ago
parent 8cdb1b1c59
commit 592d061ee8
No known key found for this signature in database
GPG Key ID: 67EC1161588A00F9

@ -54,6 +54,7 @@ func actionsMap() map[string]bool {
}
}
func (ct *Cointop) actionExists(action string) bool {
// ActionExists returns true if action exists
func (ct *Cointop) ActionExists(action string) bool {
return ct.actionsMap[action]
}

@ -11,11 +11,54 @@ import (
"github.com/miguelmota/cointop/cointop/common/timeutil"
)
// ChartView is structure for chart view
type ChartView struct {
*View
}
// NewChartView returns a new chart view
func NewChartView() *ChartView {
return &ChartView{NewView("chart")}
}
var chartLock sync.Mutex
var chartPointsLock sync.Mutex
func (ct *Cointop) updateChart() error {
if ct.Views.Chart.Backing == nil {
func chartRanges() []string {
return []string{
"1H",
"6H",
"24H",
"3D",
"7D",
"1M",
"3M",
"6M",
"1Y",
"YTD",
"All Time",
}
}
func chartRangesMap() map[string]time.Duration {
return 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),
"6M": time.Duration(24 * 7 * 4 * 6 * time.Hour),
"3M": time.Duration(24 * 7 * 4 * 3 * time.Hour),
"1M": time.Duration(24 * 7 * 4 * time.Hour),
"7D": time.Duration(24 * 7 * time.Hour),
"3D": time.Duration(24 * 3 * time.Hour),
"24H": time.Duration(24 * time.Hour),
"6H": time.Duration(6 * time.Hour),
"1H": time.Duration(1 * time.Hour),
}
}
// UpdateChart updates the chart view
func (ct *Cointop) UpdateChart() error {
if ct.Views.Chart.Backing() == nil {
return nil
}
@ -23,17 +66,17 @@ func (ct *Cointop) updateChart() error {
defer chartLock.Unlock()
if ct.State.portfolioVisible {
if err := ct.portfolioChart(); err != nil {
if err := ct.PortfolioChart(); err != nil {
return err
}
} else {
symbol := ct.selectedCoinSymbol()
name := ct.selectedCoinName()
ct.calcChartPoints(symbol, name)
ct.ChartPoints(symbol, name)
}
if len(ct.State.chartPoints) != 0 {
ct.Views.Chart.Backing.Clear()
ct.Views.Chart.Backing().Clear()
}
var body string
if len(ct.State.chartPoints) == 0 {
@ -50,17 +93,18 @@ func (ct *Cointop) updateChart() error {
}
}
ct.update(func() {
if ct.Views.Chart.Backing == nil {
if ct.Views.Chart.Backing() == nil {
return
}
fmt.Fprint(ct.Views.Chart.Backing, ct.colorscheme.Chart(body))
fmt.Fprint(ct.Views.Chart.Backing(), ct.colorscheme.Chart(body))
})
return nil
}
func (ct *Cointop) calcChartPoints(symbol string, name string) error {
// ChartPoints calculates the the chart points
func (ct *Cointop) ChartPoints(symbol string, name string) error {
maxX := ct.maxTableWidth - 3
chartPointsLock.Lock()
defer chartPointsLock.Unlock()
@ -160,7 +204,8 @@ func (ct *Cointop) calcChartPoints(symbol string, name string) error {
return nil
}
func (ct *Cointop) portfolioChart() error {
// PortfolioChart renders the portfolio chart
func (ct *Cointop) PortfolioChart() error {
maxX := ct.maxTableWidth - 3
chartPointsLock.Lock()
defer chartPointsLock.Unlock()
@ -268,7 +313,8 @@ func (ct *Cointop) portfolioChart() error {
return nil
}
func (ct *Cointop) nextChartRange() error {
// NextChartRange sets the chart to the next range option
func (ct *Cointop) NextChartRange() error {
sel := 0
max := len(ct.chartRanges)
for i, k := range ct.chartRanges {
@ -283,11 +329,12 @@ func (ct *Cointop) nextChartRange() error {
ct.State.selectedChartRange = ct.chartRanges[sel]
go ct.updateChart()
go ct.UpdateChart()
return nil
}
func (ct *Cointop) prevChartRange() error {
// PrevChartRange sets the chart to the prevous range option
func (ct *Cointop) PrevChartRange() error {
sel := 0
for i, k := range ct.chartRanges {
if k == ct.State.selectedChartRange {
@ -300,31 +347,34 @@ func (ct *Cointop) prevChartRange() error {
}
ct.State.selectedChartRange = ct.chartRanges[sel]
go ct.updateChart()
go ct.UpdateChart()
return nil
}
func (ct *Cointop) firstChartRange() error {
// FirstChartRange sets the chart to the first range option
func (ct *Cointop) FirstChartRange() error {
ct.State.selectedChartRange = ct.chartRanges[0]
go ct.updateChart()
go ct.UpdateChart()
return nil
}
func (ct *Cointop) lastChartRange() error {
// LastChartRange sets the chart to the last range option
func (ct *Cointop) LastChartRange() error {
ct.State.selectedChartRange = ct.chartRanges[len(ct.chartRanges)-1]
go ct.updateChart()
go ct.UpdateChart()
return nil
}
func (ct *Cointop) toggleCoinChart() error {
highlightedcoin := ct.highlightedRowCoin()
// ToggleCoinChart toggles between the global chart and the coin chart
func (ct *Cointop) ToggleCoinChart() error {
highlightedcoin := ct.HighlightedRowCoin()
if ct.State.selectedCoin == highlightedcoin {
ct.State.selectedCoin = nil
} else {
ct.State.selectedCoin = highlightedcoin
}
go ct.updateChart()
go ct.UpdateChart()
go ct.updateMarketbar()
return nil
}

@ -26,16 +26,16 @@ var ErrInvalidAPIChoice = errors.New("Invalid API choice")
// 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
Chart *ChartView
Table *TableView
TableHeader *TableHeaderView
Marketbar *MarketbarView
SearchField *SearchFieldView
Statusbar *StatusbarView
Help *HelpView
ConvertMenu *ConvertMenuView
Input *InputView
PortfolioUpdateMenu *PortfolioUpdateMenuView
}
// State is the state preferences of cointop
@ -49,7 +49,7 @@ type State struct {
defaultView string
// DEPRECATED: favorites by 'symbol' is deprecated because of collisions.
favoritesbysymbol map[string]bool
favoritesBySymbol map[string]bool
favorites map[string]bool
filterByFavorites bool
@ -160,40 +160,16 @@ func NewCointop(config *Config) *Cointop {
actionsMap: actionsMap(),
cache: cache.New(1*time.Minute, 2*time.Minute),
configFilepath: configFilepath,
chartRanges: []string{
"1H",
"6H",
"24H",
"3D",
"7D",
"1M",
"3M",
"6M",
"1Y",
"YTD",
"All Time",
},
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),
"6M": time.Duration(24 * 7 * 4 * 6 * time.Hour),
"3M": time.Duration(24 * 7 * 4 * 3 * time.Hour),
"1M": time.Duration(24 * 7 * 4 * time.Hour),
"7D": time.Duration(24 * 7 * time.Hour),
"3D": time.Duration(24 * 3 * time.Hour),
"24H": time.Duration(24 * time.Hour),
"6H": time.Duration(6 * time.Hour),
"1H": time.Duration(1 * time.Hour),
},
limiter: time.Tick(2 * time.Second),
chartRanges: chartRanges(),
debug: debug,
chartRangesMap: chartRangesMap(),
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),
favoritesBySymbol: make(map[string]bool),
favorites: make(map[string]bool),
hideMarketbar: config.HideMarketbar,
hideChart: config.HideChart,
@ -209,53 +185,18 @@ func NewCointop(config *Config) *Cointop {
Entries: make(map[string]*PortfolioEntry, 0),
},
},
tableColumnOrder: []string{
"rank",
"name",
"symbol",
"price",
"holdings",
"balance",
"marketcap",
"24hvolume",
"1hchange",
"7dchange",
"totalsupply",
"availablesupply",
"percentholdings",
"lastupdated",
},
tableColumnOrder: tableColumnOrder(),
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",
},
Chart: NewChartView(),
Table: NewTableView(),
TableHeader: NewTableHeaderView(),
Marketbar: NewMarketbarView(),
SearchField: NewSearchFieldView(),
Statusbar: NewStatusbarView(),
Help: NewHelpView(),
ConvertMenu: NewConvertMenuView(),
Input: NewInputView(),
PortfolioUpdateMenu: NewPortfolioUpdateMenuView(),
},
}
@ -350,10 +291,10 @@ func NewCointop(config *Config) *Cointop {
// 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.State.allCoinsSlugMap {
coin := ct.State.allCoinsSlugMap[i]
for k := range ct.State.favoritesbysymbol {
for k := range ct.State.favoritesBySymbol {
if coin.Symbol == k {
ct.State.favorites[coin.Name] = true
delete(ct.State.favoritesbysymbol, k)
delete(ct.State.favoritesBySymbol, k)
}
}
}
@ -429,7 +370,7 @@ func Reset() {
Clean()
// default config path
configPath := fmt.Sprintf("%s%s", userHomeDir(), "/.cointop")
configPath := fmt.Sprintf("%s%s", UserHomeDir(), "/.cointop")
if _, err := os.Stat(configPath); !os.IsNotExist(err) {
fmt.Printf("removing %s\n", configPath)
if err := os.RemoveAll(configPath); err != nil {

@ -87,13 +87,13 @@ func (ct *Cointop) createConfigIfNotExists() error {
}
func (ct *Cointop) configDirPath() string {
path := normalizePath(ct.configFilepath)
path := NormalizePath(ct.configFilepath)
parts := strings.Split(path, "/")
return strings.Join(parts[0:len(parts)-1], "/")
}
func (ct *Cointop) configPath() string {
return normalizePath(ct.configFilepath)
return NormalizePath(ct.configFilepath)
}
func (ct *Cointop) makeConfigDir() error {
@ -166,10 +166,10 @@ func (ct *Cointop) configToToml() ([]byte, error) {
favorites = append(favorites, i)
}
}
var favoritesbysymbol []interface{}
var favoritesBySymbol []interface{}
favoritesIfcs := map[string][]interface{}{
// DEPRECATED: favorites by 'symbol' is deprecated because of collisions. Kept for backward compatibility.
"symbols": favoritesbysymbol,
"symbols": favoritesBySymbol,
"names": favorites,
}
@ -218,7 +218,7 @@ func (ct *Cointop) configToToml() ([]byte, error) {
func (ct *Cointop) loadShortcutsFromConfig() error {
for k, ifc := range ct.config.Shortcuts {
if v, ok := ifc.(string); ok {
if !ct.actionExists(v) {
if !ct.ActionExists(v) {
continue
}
if ct.State.shortcutKeys[k] == "" {
@ -291,7 +291,7 @@ func (ct *Cointop) getColorschemeColors() (map[string]interface{}, error) {
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" {
@ -328,7 +328,7 @@ func (ct *Cointop) loadFavoritesFromConfig() error {
if k == "symbols" {
for _, ifc := range arr {
if v, ok := ifc.(string); ok {
ct.State.favoritesbysymbol[strings.ToUpper(v)] = true
ct.State.favoritesBySymbol[strings.ToUpper(v)] = true
}
}
} else if k == "names" {

@ -87,6 +87,16 @@ var currencySymbol = map[string]string{
var alphanumericcharacters = []rune{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}
// ConvertMenuView is structure for convert menu view
type ConvertMenuView struct {
*View
}
// NewConvertMenuView returns a new convert menu view
func NewConvertMenuView() *ConvertMenuView {
return &ConvertMenuView{NewView("convertmenu")}
}
func (ct *Cointop) supportedCurrencyConversions() map[string]string {
all := map[string]string{}
for _, symbol := range ct.api.SupportedCurrencies() {
@ -123,7 +133,7 @@ func (ct *Cointop) updateConvertMenu() {
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.Views.ConvertMenu.Name)
h := ct.Views.ConvertMenu.Height()
percol := h - 5
cols := make([][]string, percol)
for i := range cols {
@ -163,13 +173,13 @@ func (ct *Cointop) updateConvertMenu() {
content := fmt.Sprintf("%s%s%s", header, helpline, body)
ct.update(func() {
if ct.Views.ConvertMenu.Backing == nil {
if ct.Views.ConvertMenu.Backing() == nil {
return
}
ct.Views.ConvertMenu.Backing.Clear()
ct.Views.ConvertMenu.Backing.Frame = true
fmt.Fprintln(ct.Views.ConvertMenu.Backing, content)
ct.Views.ConvertMenu.Backing().Clear()
ct.Views.ConvertMenu.Backing().Frame = true
fmt.Fprintln(ct.Views.ConvertMenu.Backing(), content)
})
}
@ -194,22 +204,22 @@ func (ct *Cointop) currencySymbol() string {
func (ct *Cointop) showConvertMenu() error {
ct.State.convertMenuVisible = true
ct.updateConvertMenu()
ct.setActiveView(ct.Views.ConvertMenu.Name)
ct.SetActiveView(ct.Views.ConvertMenu.Name())
return nil
}
func (ct *Cointop) hideConvertMenu() error {
ct.State.convertMenuVisible = false
ct.setViewOnBottom(ct.Views.ConvertMenu.Name)
ct.setActiveView(ct.Views.Table.Name)
ct.SetViewOnBottom(ct.Views.ConvertMenu.Name())
ct.SetActiveView(ct.Views.Table.Name())
ct.update(func() {
if ct.Views.ConvertMenu.Backing == nil {
if ct.Views.ConvertMenu.Backing() == nil {
return
}
ct.Views.ConvertMenu.Backing.Clear()
ct.Views.ConvertMenu.Backing.Frame = false
fmt.Fprintln(ct.Views.ConvertMenu.Backing, "")
ct.Views.ConvertMenu.Backing().Clear()
ct.Views.ConvertMenu.Backing().Frame = false
fmt.Fprintln(ct.Views.ConvertMenu.Backing(), "")
})
return nil
}

@ -1,5 +1,6 @@
package cointop
// RowChanged is called when the row is updated
func (ct *Cointop) rowChanged() {
ct.refreshRowLink()
ct.RefreshRowLink()
}

@ -2,7 +2,7 @@ package cointop
func (ct *Cointop) toggleFavorite() error {
ct.State.portfolioVisible = false
coin := ct.highlightedRowCoin()
coin := ct.HighlightedRowCoin()
if coin == nil {
return nil
}

@ -7,6 +7,16 @@ import (
"github.com/miguelmota/cointop/cointop/common/pad"
)
// HelpView is structure for help view
type HelpView struct {
*View
}
// NewHelpView returns a new help view
func NewHelpView() *HelpView {
return &HelpView{NewView("help")}
}
func (ct *Cointop) updateHelp() {
keys := make([]string, 0, len(ct.State.shortcutKeys))
for k := range ct.State.shortcutKeys {
@ -16,7 +26,7 @@ func (ct *Cointop) updateHelp() {
header := ct.colorscheme.MenuHeader(fmt.Sprintf(" Help %s\n\n", pad.Left("[q] close ", ct.maxTableWidth-10, " ")))
cnt := 0
h := ct.viewHeight(ct.Views.Help.Name)
h := ct.Views.Help.Height()
percol := h - 6
cols := make([][]string, percol)
for i := range cols {
@ -44,39 +54,39 @@ 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.Views.Help.Backing == nil {
if ct.Views.Help.Backing() == nil {
return
}
ct.Views.Help.Backing.Clear()
ct.Views.Help.Backing.Frame = true
fmt.Fprintln(ct.Views.Help.Backing, 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.State.helpVisible = true
ct.updateHelp()
ct.setActiveView(ct.Views.Help.Name)
ct.SetActiveView(ct.Views.Help.Name())
return nil
}
func (ct *Cointop) hideHelp() error {
ct.State.helpVisible = false
ct.setViewOnBottom(ct.Views.Help.Name)
ct.setActiveView(ct.Views.Table.Name)
ct.SetViewOnBottom(ct.Views.Help.Name())
ct.SetActiveView(ct.Views.Table.Name())
ct.update(func() {
if ct.Views.Help.Backing == nil {
if ct.Views.Help.Backing() == nil {
return
}
ct.Views.Help.Backing.Clear()
ct.Views.Help.Backing.Frame = false
fmt.Fprintln(ct.Views.Help.Backing, "")
ct.Views.Help.Backing().Clear()
ct.Views.Help.Backing().Frame = false
fmt.Fprintln(ct.Views.Help.Backing(), "")
})
return nil
}

@ -235,7 +235,7 @@ func (ct *Cointop) keybindings(g *gocui.Gui) error {
case "move_to_page_last_row":
fn = ct.keyfn(ct.navigateLastLine)
case "open_link":
fn = ct.keyfn(ct.openLink)
fn = ct.keyfn(ct.OpenLink)
case "refresh":
fn = ct.keyfn(ct.refresh)
case "sort_column_asc":
@ -268,7 +268,7 @@ func (ct *Cointop) keybindings(g *gocui.Gui) error {
case "sort_column_available_supply":
fn = ct.sortfn("availablesupply", true)
case "toggle_row_chart":
fn = ct.keyfn(ct.toggleCoinChart)
fn = ct.keyfn(ct.ToggleCoinChart)
case "move_to_page_visible_first_row":
fn = ct.keyfn(ct.navigatePageFirstLine)
case "move_to_page_visible_last_row":
@ -305,18 +305,18 @@ func (ct *Cointop) keybindings(g *gocui.Gui) error {
case "save":
fn = ct.keyfn(ct.save)
case "quit":
fn = ct.keyfn(ct.quit)
fn = ct.keyfn(ct.Quit)
view = ""
case "quit_view":
fn = ct.keyfn(ct.quitView)
fn = ct.keyfn(ct.QuitView)
case "next_chart_range":
fn = ct.keyfn(ct.nextChartRange)
fn = ct.keyfn(ct.NextChartRange)
case "previous_chart_range":
fn = ct.keyfn(ct.prevChartRange)
fn = ct.keyfn(ct.PrevChartRange)
case "first_chart_range":
fn = ct.keyfn(ct.firstChartRange)
fn = ct.keyfn(ct.FirstChartRange)
case "last_chart_range":
fn = ct.keyfn(ct.lastChartRange)
fn = ct.keyfn(ct.LastChartRange)
case "toggle_show_currency_convert_menu":
fn = ct.keyfn(ct.toggleConvertMenu)
case "show_currency_convert_menu":
@ -331,7 +331,7 @@ func (ct *Cointop) keybindings(g *gocui.Gui) error {
case "show_portfolio_edit_menu":
fn = ct.keyfn(ct.togglePortfolioUpdateMenu)
case "toggle_table_fullscreen":
fn = ct.keyfn(ct.toggleTableFullscreen)
fn = ct.keyfn(ct.ToggleTableFullscreen)
view = ""
default:
fn = ct.keyfn(ct.noop)
@ -341,33 +341,33 @@ func (ct *Cointop) keybindings(g *gocui.Gui) error {
}
// keys to force quit
ct.setKeybindingMod(gocui.KeyCtrlC, gocui.ModNone, ct.keyfn(ct.quit), "")
ct.setKeybindingMod(gocui.KeyCtrlZ, gocui.ModNone, ct.keyfn(ct.quit), "")
ct.setKeybindingMod(gocui.KeyCtrlC, gocui.ModNone, ct.keyfn(ct.Quit), "")
ct.setKeybindingMod(gocui.KeyCtrlZ, gocui.ModNone, ct.keyfn(ct.Quit), "")
// searchfield keys
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)
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.Views.Help.Name)
ct.setKeybindingMod('q', gocui.ModNone, ct.keyfn(ct.hideHelp), ct.Views.Help.Name)
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.Views.Input.Name)
ct.setKeybindingMod('q', gocui.ModNone, ct.keyfn(ct.hidePortfolioUpdateMenu), ct.Views.Input.Name)
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.Views.Input.Name)
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.Views.ConvertMenu.Name)
ct.setKeybindingMod('q', gocui.ModNone, ct.keyfn(ct.hideConvertMenu), ct.Views.ConvertMenu.Name)
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.Views.ConvertMenu.Name)
ct.setKeybindingMod(rune(alphanumericcharacters[i]), gocui.ModNone, ct.keyfn(ct.setCurrencyConverstion(k)), ct.Views.ConvertMenu.Name())
}
return nil

@ -38,80 +38,80 @@ func (ct *Cointop) layout(g *gocui.Gui) error {
}
if !ct.State.hideMarketbar {
if v, err := g.SetView(ct.Views.Marketbar.Name, 0, topOffset, maxX, 2); err != nil {
if v, err := g.SetView(ct.Views.Marketbar.Name(), 0, topOffset, maxX, 2); err != nil {
if err != gocui.ErrUnknownView {
return err
}
ct.Views.Marketbar.Backing = v
ct.Views.Marketbar.Backing.Frame = false
ct.colorscheme.SetViewColor(ct.Views.Marketbar.Backing, "marketbar")
ct.Views.Marketbar.SetBacking(v)
ct.Views.Marketbar.Backing().Frame = false
ct.colorscheme.SetViewColor(ct.Views.Marketbar.Backing(), "marketbar")
go func() {
ct.updateMarketbar()
_, found := ct.cache.Get(ct.Views.Marketbar.Name)
_, found := ct.cache.Get(ct.Views.Marketbar.Name())
if found {
ct.cache.Delete(ct.Views.Marketbar.Name)
ct.cache.Delete(ct.Views.Marketbar.Name())
ct.updateMarketbar()
}
}()
}
} else {
if ct.Views.Marketbar.Backing != nil {
if err := g.DeleteView(ct.Views.Marketbar.Name); err != nil {
if ct.Views.Marketbar.Backing() != nil {
if err := g.DeleteView(ct.Views.Marketbar.Name()); err != nil {
return err
}
ct.Views.Marketbar.Backing = nil
ct.Views.Marketbar.SetBacking(nil)
}
}
topOffset = topOffset + marketbarHeight
if !ct.State.hideChart {
if v, err := g.SetView(ct.Views.Chart.Name, 0, topOffset, maxX, topOffset+chartHeight+marketbarHeight); err != nil {
if v, err := g.SetView(ct.Views.Chart.Name(), 0, topOffset, maxX, topOffset+chartHeight+marketbarHeight); err != nil {
if err != gocui.ErrUnknownView {
return err
}
ct.Views.Chart.Backing = v
ct.Views.Chart.Backing.Frame = false
ct.colorscheme.SetViewColor(ct.Views.Chart.Backing, "chart")
ct.Views.Chart.SetBacking(v)
ct.Views.Chart.Backing().Frame = false
ct.colorscheme.SetViewColor(ct.Views.Chart.Backing(), "chart")
go func() {
ct.updateChart()
ct.UpdateChart()
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)
ct.updateChart()
ct.UpdateChart()
}
}()
}
} else {
if ct.Views.Chart.Backing != nil {
if err := g.DeleteView(ct.Views.Chart.Name); err != nil {
if ct.Views.Chart.Backing() != nil {
if err := g.DeleteView(ct.Views.Chart.Name()); err != nil {
return err
}
ct.Views.Chart.Backing = nil
ct.Views.Chart.SetBacking(nil)
}
}
topOffset = topOffset + chartHeight
if v, err := g.SetView(ct.Views.Header.Name, 0, topOffset, ct.maxTableWidth, topOffset+2); err != nil {
if v, err := g.SetView(ct.Views.TableHeader.Name(), 0, topOffset, ct.maxTableWidth, topOffset+2); err != nil {
if err != gocui.ErrUnknownView {
return err
}
ct.Views.Header.Backing = v
ct.Views.Header.Backing.Frame = false
ct.colorscheme.SetViewColor(ct.Views.Header.Backing, "table_header")
go ct.updateHeaders()
ct.Views.TableHeader.SetBacking(v)
ct.Views.TableHeader.Backing().Frame = false
ct.colorscheme.SetViewColor(ct.Views.TableHeader.Backing(), "table_header")
go ct.updateTableHeader()
}
topOffset = topOffset + headerHeight
if v, err := g.SetView(ct.Views.Table.Name, 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.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")
ct.Views.Table.SetBacking(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")
@ -123,81 +123,81 @@ func (ct *Cointop) layout(g *gocui.Gui) error {
}
if !ct.State.hideStatusbar {
if v, err := g.SetView(ct.Views.Statusbar.Name, 0, maxY-statusbarHeight-1, ct.maxTableWidth, maxY); err != nil {
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.Views.Statusbar.Backing = v
ct.Views.Statusbar.Backing.Frame = false
ct.colorscheme.SetViewColor(ct.Views.Statusbar.Backing, "statusbar")
ct.Views.Statusbar.SetBacking(v)
ct.Views.Statusbar.Backing().Frame = false
ct.colorscheme.SetViewColor(ct.Views.Statusbar.Backing(), "statusbar")
go ct.updateStatusbar("")
}
} else {
if ct.Views.Statusbar.Backing != nil {
if err := g.DeleteView(ct.Views.Statusbar.Name); err != nil {
if ct.Views.Statusbar.Backing() != nil {
if err := g.DeleteView(ct.Views.Statusbar.Name()); err != nil {
return err
}
ct.Views.Statusbar.Backing = nil
ct.Views.Statusbar.SetBacking(nil)
}
}
if v, err := g.SetView(ct.Views.SearchField.Name, 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.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")
ct.Views.SearchField.SetBacking(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.Views.Help.Name, 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.Views.Help.Backing = v
ct.Views.Help.Backing.Frame = false
ct.colorscheme.SetViewColor(ct.Views.Help.Backing, "menu")
ct.Views.Help.SetBacking(v)
ct.Views.Help.Backing().Frame = false
ct.colorscheme.SetViewColor(ct.Views.Help.Backing(), "menu")
}
if v, err := g.SetView(ct.Views.PortfolioUpdateMenu.Name, 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.Views.PortfolioUpdateMenu.Backing = v
ct.Views.PortfolioUpdateMenu.Backing.Frame = false
ct.colorscheme.SetViewColor(ct.Views.PortfolioUpdateMenu.Backing, "menu")
ct.Views.PortfolioUpdateMenu.SetBacking(v)
ct.Views.PortfolioUpdateMenu.Backing().Frame = false
ct.colorscheme.SetViewColor(ct.Views.PortfolioUpdateMenu.Backing(), "menu")
}
if v, err := g.SetView(ct.Views.Input.Name, 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.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")
ct.Views.Input.SetBacking(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.Views.ConvertMenu.Name, 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.Views.ConvertMenu.Backing = v
ct.Views.ConvertMenu.Backing.Frame = false
ct.colorscheme.SetViewColor(ct.Views.ConvertMenu.Backing, "menu")
ct.Views.ConvertMenu.SetBacking(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.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)
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()
}

@ -12,6 +12,16 @@ import (
"github.com/miguelmota/cointop/cointop/common/pad"
)
// MarketbarView is structure for marketbar view
type MarketbarView struct {
*View
}
// NewMarketbarView returns a new marketbar view
func NewMarketbarView() *MarketbarView {
return &MarketbarView{NewView("marketbar")}
}
func (ct *Cointop) updateMarketbar() error {
if ct.Views.Marketbar.Backing == nil {
return nil
@ -133,12 +143,12 @@ func (ct *Cointop) updateMarketbar() error {
content = ct.colorscheme.Marketbar(content)
ct.update(func() {
if ct.Views.Marketbar.Backing == nil {
if ct.Views.Marketbar.Backing() == nil {
return
}
ct.Views.Marketbar.Backing.Clear()
fmt.Fprintln(ct.Views.Marketbar.Backing, content)
ct.Views.Marketbar.Backing().Clear()
fmt.Fprintln(ct.Views.Marketbar.Backing(), content)
})
return nil

@ -28,19 +28,19 @@ func (ct *Cointop) setPage(page int) int {
}
func (ct *Cointop) cursorDown() error {
if ct.Views.Table.Backing == nil {
if ct.Views.Table.Backing() == nil {
return nil
}
_, y := ct.Views.Table.Backing.Origin()
cx, cy := ct.Views.Table.Backing.Cursor()
_, 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.Views.Table.Backing.SetCursor(cx, cy+1); err != nil {
ox, oy := ct.Views.Table.Backing.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.Views.Table.Backing.SetOrigin(ox, oy+1); err != nil {
if err := ct.Views.Table.Backing().SetOrigin(ox, oy+1); err != nil {
return err
}
}
@ -52,11 +52,11 @@ func (ct *Cointop) cursorUp() error {
if ct.Views.Table.Backing == nil {
return nil
}
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 {
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.Views.Table.Backing.SetOrigin(ox, oy-1); err != nil {
if err := ct.Views.Table.Backing().SetOrigin(ox, oy-1); err != nil {
return err
}
}
@ -68,9 +68,9 @@ func (ct *Cointop) pageDown() error {
if ct.Views.Table.Backing == nil {
return nil
}
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
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.State.coins)
// end of table
@ -83,12 +83,12 @@ func (ct *Cointop) pageDown() error {
sy = l
}
if err := ct.Views.Table.Backing.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.Views.Table.Backing.SetCursor(cx, sy-1); err != nil {
if err := ct.Views.Table.Backing().SetCursor(cx, sy-1); err != nil {
return err
}
}
@ -100,19 +100,19 @@ func (ct *Cointop) pageUp() error {
if ct.Views.Table.Backing == nil {
return nil
}
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
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.Views.Table.Backing.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.Views.Table.Backing.SetCursor(cx, 0); err != nil {
if err := ct.Views.Table.Backing().SetCursor(cx, 0); err != nil {
return err
}
}
@ -124,12 +124,12 @@ func (ct *Cointop) navigateFirstLine() error {
if ct.Views.Table.Backing == nil {
return nil
}
ox, _ := ct.Views.Table.Backing.Origin()
cx, _ := ct.Views.Table.Backing.Cursor()
if err := ct.Views.Table.Backing.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.Views.Table.Backing.SetCursor(cx, 0); err != nil {
if err := ct.Views.Table.Backing().SetCursor(cx, 0); err != nil {
return err
}
ct.rowChanged()
@ -140,15 +140,15 @@ func (ct *Cointop) navigateLastLine() error {
if ct.Views.Table.Backing == nil {
return nil
}
ox, _ := ct.Views.Table.Backing.Origin()
cx, _ := ct.Views.Table.Backing.Cursor()
_, sy := ct.Views.Table.Backing.Size()
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.Views.Table.Backing.SetOrigin(ox, k); err != nil {
if err := ct.Views.Table.Backing().SetOrigin(ox, k); err != nil {
return err
}
if err := ct.Views.Table.Backing.SetCursor(cx, sy-1); err != nil {
if err := ct.Views.Table.Backing().SetCursor(cx, sy-1); err != nil {
return err
}
ct.rowChanged()
@ -159,8 +159,8 @@ func (ct *Cointop) navigatePageFirstLine() error {
if ct.Views.Table.Backing == nil {
return nil
}
cx, _ := ct.Views.Table.Backing.Cursor()
if err := ct.Views.Table.Backing.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()
@ -171,9 +171,9 @@ func (ct *Cointop) navigatePageMiddleLine() error {
if ct.Views.Table.Backing == nil {
return 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 {
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()
@ -184,9 +184,9 @@ func (ct *Cointop) navigatePageLastLine() error {
if ct.Views.Table.Backing == nil {
return 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 {
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()
@ -239,19 +239,19 @@ func (ct *Cointop) goToGlobalIndex(idx int) error {
}
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()
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().SetOrigin(ox, oy)
}
ct.Views.Table.Backing.SetCursor(cx, cy)
ct.Views.Table.Backing().SetCursor(cx, cy)
return nil
}

@ -10,6 +10,16 @@ import (
"github.com/miguelmota/cointop/cointop/common/pad"
)
// PortfolioUpdateMenuView is structure for portfolio update menu view
type PortfolioUpdateMenuView struct {
*View
}
// NewPortfolioUpdateMenuView returns a new portfolio update menu view
func NewPortfolioUpdateMenuView() *PortfolioUpdateMenuView {
return &PortfolioUpdateMenuView{NewView("portfolioupdatemenu")}
}
func (ct *Cointop) togglePortfolio() error {
ct.State.filterByFavorites = false
ct.State.portfolioVisible = !ct.State.portfolioVisible
@ -33,7 +43,7 @@ func (ct *Cointop) togglePortfolioUpdateMenu() error {
}
func (ct *Cointop) updatePortfolioUpdateMenu() {
coin := ct.highlightedRowCoin()
coin := ct.HighlightedRowCoin()
exists := ct.PortfolioEntryExists(coin)
value := strconv.FormatFloat(coin.Holdings, 'f', -1, 64)
var mode string
@ -52,16 +62,16 @@ func (ct *Cointop) updatePortfolioUpdateMenu() {
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.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)
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)
})
}
func (ct *Cointop) showPortfolioUpdateMenu() error {
coin := ct.highlightedRowCoin()
coin := ct.HighlightedRowCoin()
if coin == nil {
ct.togglePortfolio()
return nil
@ -69,26 +79,26 @@ func (ct *Cointop) showPortfolioUpdateMenu() error {
ct.State.portfolioUpdateMenuVisible = true
ct.updatePortfolioUpdateMenu()
ct.setActiveView(ct.Views.PortfolioUpdateMenu.Name)
ct.SetActiveView(ct.Views.PortfolioUpdateMenu.Name())
return nil
}
func (ct *Cointop) hidePortfolioUpdateMenu() error {
ct.State.portfolioUpdateMenuVisible = false
ct.setViewOnBottom(ct.Views.PortfolioUpdateMenu.Name)
ct.setViewOnBottom(ct.Views.Input.Name)
ct.setActiveView(ct.Views.Table.Name)
ct.SetViewOnBottom(ct.Views.PortfolioUpdateMenu.Name())
ct.SetViewOnBottom(ct.Views.Input.Name())
ct.SetActiveView(ct.Views.Table.Name())
ct.update(func() {
if ct.Views.PortfolioUpdateMenu.Backing == nil {
if ct.Views.PortfolioUpdateMenu.Backing() == nil {
return
}
ct.Views.PortfolioUpdateMenu.Backing.Clear()
ct.Views.PortfolioUpdateMenu.Backing.Frame = false
fmt.Fprintln(ct.Views.PortfolioUpdateMenu.Backing, "")
ct.Views.PortfolioUpdateMenu.Backing().Clear()
ct.Views.PortfolioUpdateMenu.Backing().Frame = false
fmt.Fprintln(ct.Views.PortfolioUpdateMenu.Backing(), "")
ct.Views.Input.Backing.Clear()
fmt.Fprintln(ct.Views.Input.Backing, "")
ct.Views.Input.Backing().Clear()
fmt.Fprintln(ct.Views.Input.Backing(), "")
})
return nil
}
@ -96,10 +106,10 @@ func (ct *Cointop) hidePortfolioUpdateMenu() error {
// sets portfolio entry holdings from inputed value
func (ct *Cointop) setPortfolioHoldings() error {
defer ct.hidePortfolioUpdateMenu()
coin := ct.highlightedRowCoin()
coin := ct.HighlightedRowCoin()
b := make([]byte, 100)
n, err := ct.Views.Input.Backing.Read(b)
n, err := ct.Views.Input.Backing().Read(b)
if n == 0 {
return nil
}
@ -129,6 +139,7 @@ func (ct *Cointop) setPortfolioHoldings() error {
return nil
}
// PortfolioEntry returns a portfolio entry
func (ct *Cointop) PortfolioEntry(c *Coin) (*PortfolioEntry, bool) {
if c == nil {
return &PortfolioEntry{}, true
@ -171,6 +182,7 @@ func (ct *Cointop) removePortfolioEntry(coin string) {
delete(ct.State.portfolio.Entries, strings.ToLower(coin))
}
// PortfolioEntryExists returns true if portfolio entry exists
func (ct *Cointop) PortfolioEntryExists(c *Coin) bool {
_, isNew := ct.PortfolioEntry(c)
return !isNew

@ -6,11 +6,13 @@ import (
"github.com/jroimartin/gocui"
)
func (ct *Cointop) quit() error {
// Quit quites the program
func (ct *Cointop) Quit() error {
return gocui.ErrQuit
}
func (ct *Cointop) quitView() error {
// QuitView exists the current view
func (ct *Cointop) QuitView() error {
if ct.State.portfolioVisible {
ct.State.portfolioVisible = false
return ct.updateTable()
@ -19,14 +21,14 @@ func (ct *Cointop) quitView() error {
ct.State.filterByFavorites = false
return ct.updateTable()
}
if ct.activeViewName() == ct.Views.Table.Name {
return ct.quit()
if ct.ActiveViewName() == ct.Views.Table.Name() {
return ct.Quit()
}
return nil
}
// Exit safely exit application
// Exit safely exits the program
func (ct *Cointop) Exit() {
if ct.g != nil {
ct.g.Close()

@ -23,7 +23,7 @@ func (ct *Cointop) refreshAll() error {
ct.updateCoins()
ct.updateTable()
}()
go ct.updateChart()
go ct.UpdateChart()
return nil
}

@ -7,28 +7,48 @@ import (
"github.com/miguelmota/cointop/cointop/common/levenshtein"
)
// SearchFieldView is structure for search field view
type SearchFieldView struct {
*View
}
// NewSearchFieldView returns a new search field view
func NewSearchFieldView() *SearchFieldView {
return &SearchFieldView{NewView("searchfield")}
}
// InputView is structure for help view
type InputView struct {
*View
}
// NewInputView returns a new help view
func NewInputView() *InputView {
return &InputView{NewView("input")}
}
func (ct *Cointop) openSearch() error {
ct.State.searchFieldVisible = true
ct.setActiveView(ct.Views.SearchField.Name)
ct.SetActiveView(ct.Views.SearchField.Name())
return nil
}
func (ct *Cointop) cancelSearch() error {
ct.State.searchFieldVisible = false
ct.setActiveView(ct.Views.Table.Name)
ct.SetActiveView(ct.Views.Table.Name())
return nil
}
func (ct *Cointop) doSearch() error {
ct.Views.SearchField.Backing.Rewind()
ct.Views.SearchField.Backing().Rewind()
b := make([]byte, 100)
n, err := ct.Views.SearchField.Backing.Read(b)
n, err := ct.Views.SearchField.Backing().Read(b)
// TODO: do this a better way (SoC)
ct.State.filterByFavorites = false
ct.State.portfolioVisible = false
defer ct.setActiveView(ct.Views.Table.Name)
defer ct.SetActiveView(ct.Views.Table.Name())
if err != nil {
return nil
}

@ -26,13 +26,3 @@ func (ct *Cointop) viewWidth(view string) int {
w, _ := v.Size()
return w
}
// viewHeight returns view height
func (ct *Cointop) viewHeight(view string) int {
v, err := ct.g.View(view)
if err != nil {
return 0
}
_, h := v.Size()
return h
}

@ -67,7 +67,7 @@ func (ct *Cointop) sort(sortBy string, desc bool, list []*Coin, renderHeaders bo
})
if renderHeaders {
ct.updateHeaders()
ct.updateTableHeader()
}
}

@ -7,11 +7,30 @@ import (
"github.com/miguelmota/cointop/cointop/common/pad"
)
func (ct *Cointop) updateStatusbar(s string) error {
if ct.Views.Statusbar.Backing == nil {
// StatusbarView is structure for statusbar view
type StatusbarView struct {
*View
}
// NewStatusbarView returns a new statusbar view
func NewStatusbarView() *StatusbarView {
return &StatusbarView{NewView("statusbar")}
}
// Update updates the content of the statusbar
func (statusbar *StatusbarView) Update(str string) error {
if statusbar.Backing() == nil {
return nil
}
statusbar.Backing().Clear()
fmt.Fprintln(statusbar.Backing(), str)
return nil
}
// updateStatusbar updates the statusbar view
func (ct *Cointop) updateStatusbar(s string) error {
currpage := ct.currentDisplayPage()
totalpages := ct.totalPagesDisplay()
var quitText string
@ -33,29 +52,26 @@ func (ct *Cointop) updateStatusbar(s string) error {
favoritesText = "[F]Favorites"
}
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, " ")
v := fmt.Sprintf("v%s", ct.Version())
str = str[:len(str)-len(v)+2] + v
ct.update(func() {
if ct.Views.Statusbar.Backing == nil {
return
}
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, " ")
v := fmt.Sprintf("v%s", ct.version())
str = str[:len(str)-len(v)+2] + v
fmt.Fprintln(ct.Views.Statusbar.Backing, str)
ct.Views.Statusbar.Update(str)
})
return nil
}
func (ct *Cointop) refreshRowLink() error {
// RefreshRowLink updates the row link in the statusbar
func (ct *Cointop) RefreshRowLink() error {
var shortcut string
if !open.CommandExists() {
shortcut = "[O]Open "
}
url := ct.rowLinkShort()
url := ct.RowLinkShort()
ct.updateStatusbar(fmt.Sprintf("%s%s", shortcut, url))
return nil

@ -9,6 +9,7 @@ import (
log "github.com/sirupsen/logrus"
)
// readAPIKeyFromStdin reads the user inputed API from the stdin prompt
func (ct *Cointop) readAPIKeyFromStdin(name string) string {
reader := bufio.NewReader(os.Stdin)
fmt.Printf("Enter %s API Key: ", name)

@ -13,7 +13,38 @@ import (
"github.com/miguelmota/cointop/cointop/common/table"
)
func (ct *Cointop) refreshTable() error {
// TableView is structure for table view
type TableView struct {
*View
}
// NewTableView returns a new table view
func NewTableView() *TableView {
return &TableView{NewView("table")}
}
// 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",
}
}
// RefreshTable refreshes the table
func (ct *Cointop) RefreshTable() error {
maxX := ct.width()
ct.table = table.New().SetWidth(maxX)
ct.table.HideColumHeaders = true
@ -147,27 +178,28 @@ func (ct *Cointop) refreshTable() error {
}
// highlight last row if current row is out of bounds (can happen when switching views)
currentrow := ct.highlightedRowIndex()
currentrow := ct.HighlightedRowIndex()
if len(ct.State.coins) > currentrow {
ct.highlightRow(currentrow)
}
ct.update(func() {
if ct.Views.Table.Backing == nil {
if ct.Views.Table.Backing() == nil {
return
}
ct.Views.Table.Backing.Clear()
ct.table.Format().Fprint(ct.Views.Table.Backing)
ct.Views.Table.Backing().Clear()
ct.table.Format().Fprint(ct.Views.Table.Backing())
go ct.rowChanged()
go ct.updateHeaders()
go ct.updateTableHeader()
go ct.updateMarketbar()
go ct.updateChart()
go ct.UpdateChart()
})
return nil
}
// updateTable updates the table
func (ct *Cointop) updateTable() error {
sliced := []*Coin{}
@ -186,14 +218,14 @@ func (ct *Cointop) updateTable() error {
}
}
ct.State.coins = sliced
go ct.refreshTable()
go ct.RefreshTable()
return nil
}
if ct.State.portfolioVisible {
sliced = ct.getPortfolioSlice()
ct.State.coins = sliced
go ct.refreshTable()
go ct.RefreshTable()
return nil
}
@ -226,13 +258,14 @@ func (ct *Cointop) updateTable() error {
ct.State.coins = sliced
ct.sort(ct.State.sortBy, ct.State.sortDesc, ct.State.coins, true)
go ct.refreshTable()
go ct.RefreshTable()
return nil
}
func (ct *Cointop) highlightedRowIndex() int {
_, y := ct.Views.Table.Backing.Origin()
_, cy := ct.Views.Table.Backing.Cursor()
// HighlightedRowIndex returns the index of the highlighted row
func (ct *Cointop) HighlightedRowIndex() int {
_, y := ct.Views.Table.Backing().Origin()
_, cy := ct.Views.Table.Backing().Cursor()
idx := y + cy
if idx < 0 {
idx = 0
@ -243,16 +276,18 @@ func (ct *Cointop) highlightedRowIndex() int {
return idx
}
func (ct *Cointop) highlightedRowCoin() *Coin {
idx := ct.highlightedRowIndex()
// HighlightedRowCoin returns the coin at the index of the highlighted row
func (ct *Cointop) HighlightedRowCoin() *Coin {
idx := ct.HighlightedRowIndex()
if len(ct.State.coins) == 0 {
return nil
}
return ct.State.coins[idx]
}
func (ct *Cointop) rowLink() string {
coin := ct.highlightedRowCoin()
// RowLink returns the row url link
func (ct *Cointop) RowLink() string {
coin := ct.HighlightedRowCoin()
if coin == nil {
return ""
}
@ -260,8 +295,9 @@ func (ct *Cointop) rowLink() string {
return ct.api.CoinLink(coin.Name)
}
func (ct *Cointop) rowLinkShort() string {
link := ct.rowLink()
// RowLinkShort returns a shortened version of the row url link
func (ct *Cointop) RowLinkShort() string {
link := ct.RowLink()
if link != "" {
u, err := url.Parse(link)
if err != nil {
@ -282,7 +318,8 @@ func (ct *Cointop) rowLinkShort() string {
return ""
}
func (ct *Cointop) toggleTableFullscreen() error {
// ToggleTableFullscreen toggles the table fullscreen mode
func (ct *Cointop) ToggleTableFullscreen() error {
ct.State.onlyTable = !ct.State.onlyTable
if ct.State.onlyTable {
} else {

@ -5,7 +5,18 @@ import (
"strings"
)
func (ct *Cointop) updateHeaders() {
// TableHeaderView is structure for table header view
type TableHeaderView struct {
*View
}
// NewTableHeaderView returns a new table header view
func NewTableHeaderView() *TableHeaderView {
return &TableHeaderView{NewView("header")}
}
// updateTableHeader renders the table header
func (ct *Cointop) updateTableHeader() {
var cols []string
type t struct {
@ -78,11 +89,11 @@ func (ct *Cointop) updateHeaders() {
}
ct.update(func() {
if ct.Views.Header.Backing == nil {
if ct.Views.TableHeader.Backing() == nil {
return
}
ct.Views.Header.Backing.Clear()
fmt.Fprintln(ct.Views.Header.Backing, strings.Join(headers, ""))
ct.Views.TableHeader.Backing().Clear()
fmt.Fprintln(ct.Views.TableHeader.Backing(), strings.Join(headers, ""))
})
}

@ -5,7 +5,7 @@ import (
log "github.com/sirupsen/logrus"
)
// update update view
// update takes a callback which updates the view
func (ct *Cointop) update(f func()) {
if ct.g == nil {
log.Fatal("gocui is not initialized")

@ -11,12 +11,14 @@ import (
"github.com/miguelmota/cointop/cointop/common/open"
)
func (ct *Cointop) openLink() error {
open.URL(ct.rowLink())
// OpenLink opens the url in a browser
func (ct *Cointop) OpenLink() error {
open.URL(ct.RowLink())
return nil
}
func getBytes(key interface{}) ([]byte, error) {
// GetBytes returns the interface in bytes form
func GetBytes(key interface{}) ([]byte, error) {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
err := enc.Encode(key)
@ -26,7 +28,8 @@ func getBytes(key interface{}) ([]byte, error) {
return buf.Bytes(), nil
}
func userHomeDir() string {
// UserHomeDir returns home directory for the user
func UserHomeDir() string {
if runtime.GOOS == "windows" {
home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
if home == "" {
@ -42,16 +45,18 @@ func userHomeDir() string {
return os.Getenv("HOME")
}
func normalizePath(path string) string {
// NormalizePath normalizes and extends the path string
func NormalizePath(path string) string {
// expand tilde
if strings.HasPrefix(path, "~/") {
path = filepath.Join(userHomeDir(), path[2:])
path = filepath.Join(UserHomeDir(), path[2:])
}
return path
}
func (ct *Cointop) slugify(s string) string {
s = strings.ToLower(s)
// Slugify returns a slugified string
func (ct *Cointop) Slugify(s string) string {
s = strings.TrimSpace(strings.ToLower(s))
return s
}

@ -3,7 +3,8 @@ package cointop
// TODO: make dynamic based on git tag
const version = "1.3.2"
func (ct *Cointop) version() string {
// Version returns the cointop version
func (ct *Cointop) Version() string {
return version
}

@ -6,34 +6,72 @@ import (
"github.com/jroimartin/gocui"
)
// IView is a cointop view
type IView interface {
Backing() *gocui.View
SetBacking(gocuiView *gocui.View)
Name() string
}
// View is a cointop view
type View struct {
Backing *gocui.View
Name string
backing *gocui.View
name string
}
// NewView creates a new view
func NewView(name string) *View {
return &View{
name: name,
}
}
// Backing returns the backing gocui view
func (view *View) Backing() *gocui.View {
return view.backing
}
// SetBacking sets the backing gocui view
func (view *View) SetBacking(gocuiView *gocui.View) {
view.backing = gocuiView
}
// Height returns thejview height
func (view *View) Height() int {
_, h := view.backing.Size()
return h
}
// Name returns the view's name
func (view *View) Name() string {
return view.name
}
func (ct *Cointop) setActiveView(v string) error {
// SetActiveView sets the active view
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.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)
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 {
// ActiveViewName returns the name of the active view
func (ct *Cointop) ActiveViewName() string {
return ct.g.CurrentView().Name()
}
func (ct *Cointop) setViewOnBottom(v string) error {
// SetViewOnBottom sets the view to the bottom layer
func (ct *Cointop) SetViewOnBottom(v string) error {
_, err := ct.g.SetViewOnBottom(v)
return err
}

Loading…
Cancel
Save