diff --git a/cointop/actions.go b/cointop/actions.go index c830f1e..3eea26e 100644 --- a/cointop/actions.go +++ b/cointop/actions.go @@ -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] } diff --git a/cointop/chart.go b/cointop/chart.go index 7c5ef2f..a1870d1 100644 --- a/cointop/chart.go +++ b/cointop/chart.go @@ -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 } diff --git a/cointop/cointop.go b/cointop/cointop.go index 55548e4..8530247 100644 --- a/cointop/cointop.go +++ b/cointop/cointop.go @@ -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 { diff --git a/cointop/config.go b/cointop/config.go index d548e51..1e58ddd 100644 --- a/cointop/config.go +++ b/cointop/config.go @@ -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" { diff --git a/cointop/conversion.go b/cointop/conversion.go index d0bb0d1..9c8b832 100644 --- a/cointop/conversion.go +++ b/cointop/conversion.go @@ -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 } diff --git a/cointop/events.go b/cointop/events.go index 53db62f..e332d0a 100644 --- a/cointop/events.go +++ b/cointop/events.go @@ -1,5 +1,6 @@ package cointop +// RowChanged is called when the row is updated func (ct *Cointop) rowChanged() { - ct.refreshRowLink() + ct.RefreshRowLink() } diff --git a/cointop/favorites.go b/cointop/favorites.go index 2531214..3cd88c0 100644 --- a/cointop/favorites.go +++ b/cointop/favorites.go @@ -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 } diff --git a/cointop/help.go b/cointop/help.go index 0e8f2b6..d60083e 100644 --- a/cointop/help.go +++ b/cointop/help.go @@ -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 } diff --git a/cointop/keybindings.go b/cointop/keybindings.go index 1f7191a..14b6c2d 100644 --- a/cointop/keybindings.go +++ b/cointop/keybindings.go @@ -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 diff --git a/cointop/layout.go b/cointop/layout.go index 1c2a586..624ffc7 100644 --- a/cointop/layout.go +++ b/cointop/layout.go @@ -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() } diff --git a/cointop/marketbar.go b/cointop/marketbar.go index 4051a3b..9eebe2a 100644 --- a/cointop/marketbar.go +++ b/cointop/marketbar.go @@ -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 diff --git a/cointop/navigation.go b/cointop/navigation.go index 4b83105..9f5d15f 100644 --- a/cointop/navigation.go +++ b/cointop/navigation.go @@ -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 } diff --git a/cointop/portfolio.go b/cointop/portfolio.go index 62734d1..ae0e7ac 100644 --- a/cointop/portfolio.go +++ b/cointop/portfolio.go @@ -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 diff --git a/cointop/quit.go b/cointop/quit.go index a940fc4..b7574ed 100644 --- a/cointop/quit.go +++ b/cointop/quit.go @@ -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() diff --git a/cointop/refresh.go b/cointop/refresh.go index 6aa2283..677ab47 100644 --- a/cointop/refresh.go +++ b/cointop/refresh.go @@ -23,7 +23,7 @@ func (ct *Cointop) refreshAll() error { ct.updateCoins() ct.updateTable() }() - go ct.updateChart() + go ct.UpdateChart() return nil } diff --git a/cointop/search.go b/cointop/search.go index 7b80852..722234d 100644 --- a/cointop/search.go +++ b/cointop/search.go @@ -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 } diff --git a/cointop/size.go b/cointop/size.go index c45e217..4da0f30 100644 --- a/cointop/size.go +++ b/cointop/size.go @@ -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 -} diff --git a/cointop/sort.go b/cointop/sort.go index c495387..ef6a17b 100644 --- a/cointop/sort.go +++ b/cointop/sort.go @@ -67,7 +67,7 @@ func (ct *Cointop) sort(sortBy string, desc bool, list []*Coin, renderHeaders bo }) if renderHeaders { - ct.updateHeaders() + ct.updateTableHeader() } } diff --git a/cointop/statusbar.go b/cointop/statusbar.go index cdaf414..85028e9 100644 --- a/cointop/statusbar.go +++ b/cointop/statusbar.go @@ -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 diff --git a/cointop/stdin.go b/cointop/stdin.go index f7dece9..e1d61bc 100644 --- a/cointop/stdin.go +++ b/cointop/stdin.go @@ -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) diff --git a/cointop/table.go b/cointop/table.go index 9373fd9..52ffb36 100644 --- a/cointop/table.go +++ b/cointop/table.go @@ -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 { diff --git a/cointop/table_header.go b/cointop/table_header.go index ee2a28b..1f2ac7f 100644 --- a/cointop/table_header.go +++ b/cointop/table_header.go @@ -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, "")) }) } diff --git a/cointop/update.go b/cointop/update.go index 3a33907..293205d 100644 --- a/cointop/update.go +++ b/cointop/update.go @@ -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") diff --git a/cointop/util.go b/cointop/util.go index ae25ce5..022eacc 100644 --- a/cointop/util.go +++ b/cointop/util.go @@ -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 } diff --git a/cointop/version.go b/cointop/version.go index 4631bb2..a7d9105 100644 --- a/cointop/version.go +++ b/cointop/version.go @@ -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 } diff --git a/cointop/view.go b/cointop/view.go index 7972997..d955be9 100644 --- a/cointop/view.go +++ b/cointop/view.go @@ -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 }