From 1d293631851f3af7f4680b5d4209c59a24b0f646 Mon Sep 17 00:00:00 2001 From: Miguel Mota Date: Mon, 13 Sep 2021 22:07:13 -0700 Subject: [PATCH] Add keybinding to toggle hide portfolio balances --- cmd/commands/holdings.go | 3 + cmd/commands/root.go | 35 ++++---- cointop/cointop.go | 37 ++++---- cointop/default_shortcuts.go | 163 ++++++++++++++++++----------------- cointop/keybindings.go | 2 + cointop/marketbar.go | 4 + cointop/portfolio.go | 22 +++++ docs/content/faq.md | 4 + 8 files changed, 156 insertions(+), 114 deletions(-) diff --git a/cmd/commands/holdings.go b/cmd/commands/holdings.go index 8f3cef3..47b8267 100644 --- a/cmd/commands/holdings.go +++ b/cmd/commands/holdings.go @@ -22,6 +22,7 @@ func HoldingsCmd() *cobra.Command { var filter []string var cols []string var convert string + var hideBalances bool holdingsCmd := &cobra.Command{ Use: "holdings", @@ -68,6 +69,7 @@ func HoldingsCmd() *cobra.Command { Cols: cols, Convert: convert, NoHeader: noHeader, + HideBalances: hideBalances, }) }, } @@ -78,6 +80,7 @@ func HoldingsCmd() *cobra.Command { holdingsCmd.Flags().BoolVarP(&noCache, "no-cache", "", noCache, "No cache") holdingsCmd.Flags().BoolVarP(&humanReadable, "human", "h", humanReadable, "Human readable output") holdingsCmd.Flags().BoolVarP(&noHeader, "no-header", "", noHeader, "Don't display header columns") + holdingsCmd.Flags().BoolVarP(&hideBalances, "hide-balances", "", hideBalances, "Hide portfolio balances. Useful for when sharing screen or taking screenshotss") holdingsCmd.Flags().StringVarP(&config, "config", "c", "", fmt.Sprintf("Config filepath. (default %s)", cointop.DefaultConfigFilepath)) holdingsCmd.Flags().StringVarP(&sortBy, "sort-by", "s", sortBy, `Sort by column. Options are "name", "symbol", "price", "holdings", "balance", "24h"`) holdingsCmd.Flags().BoolVarP(&sortDesc, "sort-desc", "d", sortDesc, "Sort in descending order") diff --git a/cmd/commands/root.go b/cmd/commands/root.go index 4aab35f..160c4be 100644 --- a/cmd/commands/root.go +++ b/cmd/commands/root.go @@ -20,6 +20,7 @@ func RootCmd() *cobra.Command { hideChart := getEnvBool("COINTOP_HIDE_CHART") hideTable := getEnvBool("COINTOP_HIDE_TABLE") hideStatusbar := getEnvBool("COINTOP_HIDE_STATUSBAR") + hidePortfolioBalances := getEnvBool("COINTOP_HIDE_PORTFOLIO_BALANCES") onlyTable := getEnvBool("COINTOP_ONLY_TABLE") onlyChart := getEnvBool("COINTOP_ONLY_CHART") silent := getEnvBool("COINTOP_SILENT") @@ -90,22 +91,23 @@ See git.io/cointop for more info.`, } ct, err := cointop.NewCointop(&cointop.Config{ - CacheDir: cacheDir, - ColorsDir: colorsDir, - NoCache: noCache, - ConfigFilepath: config, - CoinMarketCapAPIKey: cmcAPIKey, - APIChoice: apiChoice, - Colorscheme: colorscheme, - HideMarketbar: hideMarketbar, - HideChart: hideChart, - HideTable: hideTable, - HideStatusbar: hideStatusbar, - OnlyTable: onlyTable, - OnlyChart: onlyChart, - RefreshRate: refreshRateP, - PerPage: perPage, - MaxPages: maxPages, + CacheDir: cacheDir, + ColorsDir: colorsDir, + NoCache: noCache, + ConfigFilepath: config, + CoinMarketCapAPIKey: cmcAPIKey, + APIChoice: apiChoice, + Colorscheme: colorscheme, + HideMarketbar: hideMarketbar, + HideChart: hideChart, + HideTable: hideTable, + HideStatusbar: hideStatusbar, + OnlyTable: onlyTable, + OnlyChart: onlyChart, + RefreshRate: refreshRateP, + PerPage: perPage, + MaxPages: maxPages, + HidePortfolioBalances: hidePortfolioBalances, }) if err != nil { return err @@ -123,6 +125,7 @@ See git.io/cointop for more info.`, rootCmd.Flags().BoolVarP(&hideChart, "hide-chart", "", hideChart, "Hide the chart view") rootCmd.Flags().BoolVarP(&hideTable, "hide-table", "", hideTable, "Hide the table view") rootCmd.Flags().BoolVarP(&hideStatusbar, "hide-statusbar", "", hideStatusbar, "Hide the bottom statusbar") + rootCmd.Flags().BoolVarP(&hidePortfolioBalances, "hide-portfolio-balances", "", hidePortfolioBalances, "Hide portfolio balances. Useful for when sharing screen or taking screenshots") rootCmd.Flags().BoolVarP(&onlyTable, "only-table", "", onlyTable, "Show only the table. Hides the chart and top and bottom bars") rootCmd.Flags().BoolVarP(&onlyChart, "only-chart", "", onlyChart, "Show only the chart. Hides the table and top and bottom bars") rootCmd.Flags().BoolVarP(&silent, "silent", "s", silent, "Silence log ouput") diff --git a/cointop/cointop.go b/cointop/cointop.go index 98a1d03..dbbaa89 100644 --- a/cointop/cointop.go +++ b/cointop/cointop.go @@ -57,6 +57,7 @@ type State struct { hideChart bool hideTable bool hideStatusbar bool + hidePortfolioBalances bool keepRowFocusOnSort bool lastSelectedRowIndex int marketBarHeight int @@ -147,23 +148,24 @@ type PriceAlerts struct { // Config config options type Config struct { - APIChoice string - CacheDir string - ColorsDir string - Colorscheme string - ConfigFilepath string - CoinMarketCapAPIKey string - NoPrompts bool - HideMarketbar bool - HideChart bool - HideTable bool - HideStatusbar bool - NoCache bool - OnlyTable bool - OnlyChart bool - RefreshRate *uint - PerPage uint - MaxPages uint + APIChoice string + CacheDir string + ColorsDir string + Colorscheme string + ConfigFilepath string + CoinMarketCapAPIKey string + NoPrompts bool + HideMarketbar bool + HideChart bool + HideTable bool + HideStatusbar bool + HidePortfolioBalances bool + NoCache bool + OnlyTable bool + OnlyChart bool + RefreshRate *uint + PerPage uint + MaxPages uint } // APIKeys is api keys structure @@ -251,6 +253,7 @@ func NewCointop(config *Config) (*Cointop, error) { hideChart: config.HideChart, hideTable: config.HideTable, hideStatusbar: config.HideStatusbar, + hidePortfolioBalances: config.HidePortfolioBalances, keepRowFocusOnSort: false, marketBarHeight: 1, maxPages: int(maxPages), diff --git a/cointop/default_shortcuts.go b/cointop/default_shortcuts.go index f1bdafa..8dc8ff1 100644 --- a/cointop/default_shortcuts.go +++ b/cointop/default_shortcuts.go @@ -3,86 +3,87 @@ package cointop // DefaultShortcuts is a map of the default shortcuts func DefaultShortcuts() map[string]string { return map[string]string{ - "up": "move_up", - "down": "move_down", - "left": "previous_page", - "right": "next_page", - "pagedown": "page_down", - "pageup": "page_up", - "home": "move_to_page_first_row", - "end": "move_to_page_last_row", - "enter": "toggle_row_chart", - "esc": "quit_view", - "space": "toggle_favorite", - "tab": "move_down_or_next_page", - "ctrl+c": "quit", - "ctrl+C": "quit", - "ctrl+d": "page_down", - "ctrl+f": "open_search", - "ctrl+n": "next_page", - "ctrl+p": "previous_page", - "ctrl+r": "refresh", - "ctrl+R": "refresh", - "ctrl+s": "save", - "ctrl+S": "save", - "ctrl+u": "page_up", - "ctrl+j": "enlarge_chart", - "ctrl+k": "shorten_chart", - "|": "toggle_chart_fullscreen", - "alt+up": "sort_column_asc", - "alt+down": "sort_column_desc", - "alt+left": "sort_left_column", - "alt+right": "sort_right_column", - "F1": "help", - "F5": "refresh", - "0": "first_page", - "1": "sort_column_1h_change", - "2": "sort_column_24h_change", - "3": "sort_column_30d_change", - "7": "sort_column_7d_change", - "a": "sort_column_available_supply", - "b": "sort_column_balance", - "c": "show_currency_convert_menu", - "C": "show_currency_convert_menu", - "e": "show_portfolio_edit_menu", - "E": "show_portfolio_edit_menu", - "A": "toggle_price_alerts", - "f": "toggle_favorite", - "F": "toggle_show_favorites", - "g": "move_to_page_first_row", - "G": "move_to_page_last_row", - "h": "previous_page", - "H": "move_to_page_visible_first_row", - "j": "move_down", - "k": "move_up", - "l": "next_page", - "L": "move_to_page_visible_last_row", - "m": "sort_column_market_cap", - "M": "move_to_page_visible_middle_row", - "n": "sort_column_name", - "o": "open_link", - "O": "open_link", - "p": "sort_column_price", - "P": "toggle_portfolio", - "r": "sort_column_rank", - "s": "sort_column_symbol", - "t": "sort_column_total_supply", - "u": "sort_column_last_updated", - "v": "sort_column_24h_volume", - "y": "sort_column_1y_change", - "q": "quit_view", - "Q": "quit_view", - "%": "sort_column_percent_holdings", - "$": "last_page", - "?": "help", - "/": "open_search", - "]": "next_chart_range", - "[": "previous_chart_range", - "}": "last_chart_range", - "{": "first_chart_range", - ">": "scroll_right", - "<": "scroll_left", - "+": "show_price_alert_add_menu", - "\\\\": "toggle_table_fullscreen", + "up": "move_up", + "down": "move_down", + "left": "previous_page", + "right": "next_page", + "pagedown": "page_down", + "pageup": "page_up", + "home": "move_to_page_first_row", + "end": "move_to_page_last_row", + "enter": "toggle_row_chart", + "esc": "quit_view", + "space": "toggle_favorite", + "tab": "move_down_or_next_page", + "ctrl+c": "quit", + "ctrl+C": "quit", + "ctrl+d": "page_down", + "ctrl+f": "open_search", + "ctrl+n": "next_page", + "ctrl+p": "previous_page", + "ctrl+r": "refresh", + "ctrl+R": "refresh", + "ctrl+s": "save", + "ctrl+S": "save", + "ctrl+u": "page_up", + "ctrl+j": "enlarge_chart", + "ctrl+k": "shorten_chart", + "ctrl+space": "toggle_portfolio_balances", + "|": "toggle_chart_fullscreen", + "alt+up": "sort_column_asc", + "alt+down": "sort_column_desc", + "alt+left": "sort_left_column", + "alt+right": "sort_right_column", + "F1": "help", + "F5": "refresh", + "0": "first_page", + "1": "sort_column_1h_change", + "2": "sort_column_24h_change", + "3": "sort_column_30d_change", + "7": "sort_column_7d_change", + "a": "sort_column_available_supply", + "b": "sort_column_balance", + "c": "show_currency_convert_menu", + "C": "show_currency_convert_menu", + "e": "show_portfolio_edit_menu", + "E": "show_portfolio_edit_menu", + "A": "toggle_price_alerts", + "f": "toggle_favorite", + "F": "toggle_show_favorites", + "g": "move_to_page_first_row", + "G": "move_to_page_last_row", + "h": "previous_page", + "H": "move_to_page_visible_first_row", + "j": "move_down", + "k": "move_up", + "l": "next_page", + "L": "move_to_page_visible_last_row", + "m": "sort_column_market_cap", + "M": "move_to_page_visible_middle_row", + "n": "sort_column_name", + "o": "open_link", + "O": "open_link", + "p": "sort_column_price", + "P": "toggle_portfolio", + "r": "sort_column_rank", + "s": "sort_column_symbol", + "t": "sort_column_total_supply", + "u": "sort_column_last_updated", + "v": "sort_column_24h_volume", + "y": "sort_column_1y_change", + "q": "quit_view", + "Q": "quit_view", + "%": "sort_column_percent_holdings", + "$": "last_page", + "?": "help", + "/": "open_search", + "]": "next_chart_range", + "[": "previous_chart_range", + "}": "last_chart_range", + "{": "first_chart_range", + ">": "scroll_right", + "<": "scroll_left", + "+": "show_price_alert_add_menu", + "\\\\": "toggle_table_fullscreen", } } diff --git a/cointop/keybindings.go b/cointop/keybindings.go index 3b8ed5e..cb2993c 100644 --- a/cointop/keybindings.go +++ b/cointop/keybindings.go @@ -301,6 +301,8 @@ func (ct *Cointop) SetKeybindingAction(shortcutKey string, action string) error fn = ct.Keyfn(ct.TogglePortfolio) case "toggle_show_portfolio": fn = ct.Keyfn(ct.ToggleShowPortfolio) + case "toggle_portfolio_balances": + fn = ct.Keyfn(ct.TogglePortfolioBalances) case "show_portfolio_edit_menu": fn = ct.Keyfn(ct.TogglePortfolioUpdateMenu) case "show_price_alert_edit_menu": diff --git a/cointop/marketbar.go b/cointop/marketbar.go index e2a0eec..fb50cd3 100644 --- a/cointop/marketbar.go +++ b/cointop/marketbar.go @@ -42,6 +42,10 @@ func (ct *Cointop) UpdateMarketbar() error { totalstr = humanize.Monetaryf(total, 2) } + if ct.State.hidePortfolioBalances { + totalstr = HiddenBalanceChars + } + timeframe := ct.State.selectedChartRange chartname := ct.SelectedCoinName() var charttitle string diff --git a/cointop/portfolio.go b/cointop/portfolio.go index aefb44c..1a09560 100644 --- a/cointop/portfolio.go +++ b/cointop/portfolio.go @@ -52,6 +52,9 @@ var DefaultPortfolioTableHeaders = []string{ "last_updated", } +// HiddenBalanceChars are the characters to show when hidding balances +var HiddenBalanceChars = "********" + // ValidPortfolioTableHeader returns the portfolio table headers func (ct *Cointop) ValidPortfolioTableHeader(name string) bool { for _, v := range SupportedPortfolioTableHeaders { @@ -154,6 +157,9 @@ func (ct *Cointop) GetPortfolioTable() *table.Table { }) case "balance": text := humanize.Monetaryf(coin.Balance, 2) + if ct.State.hidePortfolioBalances { + text = HiddenBalanceChars + } ct.SetTableColumnWidthFromString(header, text) ct.SetTableColumnAlignLeft(header, false) colorBalance := ct.colorscheme.TableColumnPrice @@ -634,6 +640,7 @@ type TablePrintOptions struct { Convert string NoHeader bool PercentChange24H bool + HideBalances bool } // outputFormats is list of valid output formats @@ -674,6 +681,7 @@ func (ct *Cointop) PrintHoldingsTable(options *TablePrintOptions) error { filterCols := options.Cols holdings := ct.GetPortfolioSlice() noHeader := options.NoHeader + hideBalances := options.HideBalances if format == "" { format = "table" @@ -771,6 +779,9 @@ func (ct *Cointop) PrintHoldingsTable(options *TablePrintOptions) error { } else { item[i] = strconv.FormatFloat(entry.Balance, 'f', -1, 64) } + if hideBalances { + item[i] = HiddenBalanceChars + } case "24h%": if humanReadable { item[i] = fmt.Sprintf("%s%%", humanize.Numericf(entry.PercentChange24H, 2)) @@ -1034,3 +1045,14 @@ func (ct *Cointop) IsPortfolioVisible() bool { func (ct *Cointop) PortfolioLen() int { return len(ct.GetPortfolioSlice()) } + +// TogglePortfolioBalances toggles hide/show portfolio balances. Useful for keeping balances secret when sharing screen or taking screenshots. +func (ct *Cointop) TogglePortfolioBalances() error { + ct.State.hidePortfolioBalances = !ct.State.hidePortfolioBalances + ct.UpdateUI(func() error { + go ct.UpdateChart() + go ct.UpdateTable() + return nil + }) + return nil +} diff --git a/docs/content/faq.md b/docs/content/faq.md index 6cca90d..feb1a56 100644 --- a/docs/content/faq.md +++ b/docs/content/faq.md @@ -183,6 +183,10 @@ draft: false Your portfolio is autosaved after you edit holdings. You can also press ctrl+s to manually save your portfolio holdings to the config file. +## How do I hide my portfolio balances (private mode)? + + You can run cointop with the `--hide-portfolio-balances` flag to toggle hide/show portfolio balances or use the keyboard shortcut Ctrl+space on the portfolio page. + ## I'm getting question marks or weird symbols instead of the correct characters. Make sure that your terminal has the encoding set to UTF-8 and that your terminal font supports UTF-8.