Browse Source

Rename Stocks to Markets

Also fix bug that would remove markets if a network request failed
and not show them again until Glance was restarted
Svilen Markov 1 year ago
parent
commit
1bebb88d0e

+ 12 - 12
docs/configuration.md

@@ -20,7 +20,7 @@
   - [Bookmarks](#bookmarks)
   - [Calendar](#calendar)
   - [Clock](#clock)
-  - [Stocks](#stocks)
+  - [Markets](#markets)
   - [Twitch Channels](#twitch-channels)
   - [Twitch Top Games](#twitch-top-games)
   - [iframe](#iframe)
@@ -80,8 +80,8 @@ pages:
           - type: weather
             location: London, United Kingdom
 
-          - type: stocks
-            stocks:
+          - type: markets
+            markets:
               - symbol: SPY
                 name: S&P 500
               - symbol: BTC-USD
@@ -1146,14 +1146,14 @@ Preview:
 >
 > There is currently no customizability available for the calendar. Extra features will be added in the future.
 
-### Stocks
-Display a list of stocks, their current value, change for the day and a small 21d chart. Data is taken from Yahoo Finance.
+### Markets
+Display a list of markets, their current value, change for the day and a small 21d chart. Data is taken from Yahoo Finance.
 
 Example:
 
 ```yaml
-- type: stocks
-  stocks:
+- type: markets
+  markets:
     - symbol: SPY
       name: S&P 500
     - symbol: BTC-USD
@@ -1168,21 +1168,21 @@ Example:
 
 Preview:
 
-![](images/stocks-widget-preview.png)
+![](images/markets-widget-preview.png)
 
 #### Properties
 
 | Name | Type | Required |
 | ---- | ---- | -------- |
-| stocks | array | yes |
+| markets | array | yes |
 | sort-by | string | no |
 | style | string | no |
 
-##### `stocks`
-An array of stocks for which to display information about.
+##### `markets`
+An array of markets for which to display information about.
 
 ##### `sort-by`
-By default the stocks are displayed in the order they were defined. You can customize their ordering by setting the `sort-by` property to `absolute-change` for descending order based on the stock's absolute price change.
+By default the markets are displayed in the order they were defined. You can customize their ordering by setting the `sort-by` property to `absolute-change` for descending order based on the stock's absolute price change.
 
 ##### `style`
 To make the widget scale appropriately in a `full` size column, set the style to the experimental `dynamic-columns-experimental` option.

+ 0 - 0
docs/images/stocks-widget-preview.png → docs/images/markets-widget-preview.png


+ 3 - 3
internal/assets/static/main.css

@@ -622,16 +622,16 @@ kbd:active {
     color: var(--color-text-highlight);
 }
 
-.stock-chart {
+.market-chart {
     margin-left: auto;
     width: 6.5rem;
 }
 
-.stock-chart svg {
+.market-chart svg {
     width: 100%;
 }
 
-.stock-values {
+.market-values {
     min-width: 8rem;
 }
 

+ 1 - 1
internal/assets/templates.go

@@ -26,7 +26,7 @@ var (
 	ChangeDetectionTemplate       = compileTemplate("change-detection.html", "widget-base.html")
 	VideosTemplate                = compileTemplate("videos.html", "widget-base.html", "video-card-contents.html")
 	VideosGridTemplate            = compileTemplate("videos-grid.html", "widget-base.html", "video-card-contents.html")
-	StocksTemplate                = compileTemplate("stocks.html", "widget-base.html")
+	MarketsTemplate               = compileTemplate("markets.html", "widget-base.html")
 	RSSListTemplate               = compileTemplate("rss-list.html", "widget-base.html")
 	RSSDetailedListTemplate       = compileTemplate("rss-detailed-list.html", "widget-base.html")
 	RSSHorizontalCardsTemplate    = compileTemplate("rss-horizontal-cards.html", "widget-base.html")

+ 7 - 7
internal/assets/templates/stocks.html → internal/assets/templates/markets.html

@@ -3,31 +3,31 @@
 {{ define "widget-content" }}
 {{ if ne .Style "dynamic-columns-experimental" }}
 <ul class="list list-gap-20 list-with-separator">
-    {{ range .Stocks }}
+    {{ range .Markets }}
     <li class="flex items-center gap-15">
-        {{ template "stock" . }}
+        {{ template "market" . }}
     </li>
     {{ end }}
 </ul>
 {{ else }}
 <div class="dynamic-columns">
-    {{ range .Stocks }}
+    {{ range .Markets }}
     <div class="flex items-center gap-15">
-        {{ template "stock" . }}
+        {{ template "market" . }}
     </div>
     {{ end }}
 </div>
 {{ end }}
 {{ end }}
 
-{{ define "stock" }}
+{{ define "market" }}
 <div class="min-width-0">
     <a{{ if ne "" .SymbolLink }} href="{{ .SymbolLink }}" target="_blank" rel="noreferrer"{{ end }} class="color-highlight size-h3 block text-truncate">{{ .Symbol }}</a>
     <div class="text-truncate">{{ .Name }}</div>
 </div>
 
-<a class="stock-chart" {{ if ne "" .ChartLink }} href="{{ .ChartLink }}" target="_blank" rel="noreferrer"{{ end }}>
-    <svg class="stock-chart shrink-0" viewBox="0 0 100 50">
+<a class="market-chart" {{ if ne "" .ChartLink }} href="{{ .ChartLink }}" target="_blank" rel="noreferrer"{{ end }}>
+    <svg class="market-chart shrink-0" viewBox="0 0 100 50">
         <polyline fill="none" stroke="var(--color-text-subdue)" stroke-width="1.5px" points="{{ .SvgChartPoints }}" vector-effect="non-scaling-stroke"></polyline>
     </svg>
 </a>

+ 11 - 7
internal/feed/primitives.go

@@ -85,20 +85,24 @@ var currencyToSymbol = map[string]string{
 	"PHP": "₱",
 }
 
-type Stock struct {
-	Name           string  `yaml:"name"`
-	Symbol         string  `yaml:"symbol"`
-	ChartLink      string  `yaml:"chart-link"`
-	SymbolLink     string  `yaml:"symbol-link"`
+type MarketRequest struct {
+	Name       string `yaml:"name"`
+	Symbol     string `yaml:"symbol"`
+	ChartLink  string `yaml:"chart-link"`
+	SymbolLink string `yaml:"symbol-link"`
+}
+
+type Market struct {
+	MarketRequest
 	Currency       string  `yaml:"-"`
 	Price          float64 `yaml:"-"`
 	PercentChange  float64 `yaml:"-"`
 	SvgChartPoints string  `yaml:"-"`
 }
 
-type Stocks []Stock
+type Markets []Market
 
-func (t Stocks) SortByAbsChange() {
+func (t Markets) SortByAbsChange() {
 	sort.Slice(t, func(i, j int) bool {
 		return math.Abs(t[i].PercentChange) > math.Abs(t[j].PercentChange)
 	})

+ 19 - 22
internal/feed/yahoo.go

@@ -6,7 +6,7 @@ import (
 	"net/http"
 )
 
-type stockResponseJson struct {
+type marketResponseJson struct {
 	Chart struct {
 		Result []struct {
 			Meta struct {
@@ -25,30 +25,30 @@ type stockResponseJson struct {
 }
 
 // TODO: allow changing chart time frame
-const stockChartDays = 21
+const marketChartDays = 21
 
-func FetchStocksDataFromYahoo(stockRequests Stocks) (Stocks, error) {
-	requests := make([]*http.Request, 0, len(stockRequests))
+func FetchMarketsDataFromYahoo(marketRequests []MarketRequest) (Markets, error) {
+	requests := make([]*http.Request, 0, len(marketRequests))
 
-	for i := range stockRequests {
-		request, _ := http.NewRequest("GET", fmt.Sprintf("https://query1.finance.yahoo.com/v8/finance/chart/%s?range=1mo&interval=1d", stockRequests[i].Symbol), nil)
+	for i := range marketRequests {
+		request, _ := http.NewRequest("GET", fmt.Sprintf("https://query1.finance.yahoo.com/v8/finance/chart/%s?range=1mo&interval=1d", marketRequests[i].Symbol), nil)
 		requests = append(requests, request)
 	}
 
-	job := newJob(decodeJsonFromRequestTask[stockResponseJson](defaultClient), requests)
+	job := newJob(decodeJsonFromRequestTask[marketResponseJson](defaultClient), requests)
 	responses, errs, err := workerPoolDo(job)
 
 	if err != nil {
 		return nil, fmt.Errorf("%w: %v", ErrNoContent, err)
 	}
 
-	stocks := make(Stocks, 0, len(responses))
+	markets := make(Markets, 0, len(responses))
 	var failed int
 
 	for i := range responses {
 		if errs[i] != nil {
 			failed++
-			slog.Error("Failed to fetch stock data", "symbol", stockRequests[i].Symbol, "error", errs[i])
+			slog.Error("Failed to fetch market data", "symbol", marketRequests[i].Symbol, "error", errs[i])
 			continue
 		}
 
@@ -56,14 +56,14 @@ func FetchStocksDataFromYahoo(stockRequests Stocks) (Stocks, error) {
 
 		if len(response.Chart.Result) == 0 {
 			failed++
-			slog.Error("Stock response contains no data", "symbol", stockRequests[i].Symbol)
+			slog.Error("Market response contains no data", "symbol", marketRequests[i].Symbol)
 			continue
 		}
 
 		prices := response.Chart.Result[0].Indicators.Quote[0].Close
 
-		if len(prices) > stockChartDays {
-			prices = prices[len(prices)-stockChartDays:]
+		if len(prices) > marketChartDays {
+			prices = prices[len(prices)-marketChartDays:]
 		}
 
 		previous := response.Chart.Result[0].Meta.RegularMarketPrice
@@ -80,13 +80,10 @@ func FetchStocksDataFromYahoo(stockRequests Stocks) (Stocks, error) {
 			currency = response.Chart.Result[0].Meta.Currency
 		}
 
-		stocks = append(stocks, Stock{
-			Name:       stockRequests[i].Name,
-			Symbol:     response.Chart.Result[0].Meta.Symbol,
-			SymbolLink: stockRequests[i].SymbolLink,
-			ChartLink:  stockRequests[i].ChartLink,
-			Price:      response.Chart.Result[0].Meta.RegularMarketPrice,
-			Currency:   currency,
+		markets = append(markets, Market{
+			MarketRequest: marketRequests[i],
+			Price:         response.Chart.Result[0].Meta.RegularMarketPrice,
+			Currency:      currency,
 			PercentChange: percentChange(
 				response.Chart.Result[0].Meta.RegularMarketPrice,
 				previous,
@@ -95,13 +92,13 @@ func FetchStocksDataFromYahoo(stockRequests Stocks) (Stocks, error) {
 		})
 	}
 
-	if len(stocks) == 0 {
+	if len(markets) == 0 {
 		return nil, ErrNoContent
 	}
 
 	if failed > 0 {
-		return stocks, fmt.Errorf("%w: could not fetch data for %d stock(s)", ErrPartialContent, failed)
+		return markets, fmt.Errorf("%w: could not fetch data for %d market(s)", ErrPartialContent, failed)
 	}
 
-	return stocks, nil
+	return markets, nil
 }

+ 19 - 14
internal/widget/stocks.go

@@ -9,34 +9,39 @@ import (
 	"github.com/glanceapp/glance/internal/feed"
 )
 
-// TODO: rename to Markets at some point
-type Stocks struct {
-	widgetBase `yaml:",inline"`
-	Stocks     feed.Stocks `yaml:"stocks"`
-	Sort       string      `yaml:"sort-by"`
-	Style      string      `yaml:"style"`
+type Markets struct {
+	widgetBase     `yaml:",inline"`
+	StocksRequests []feed.MarketRequest `yaml:"stocks"`
+	MarketRequests []feed.MarketRequest `yaml:"markets"`
+	Sort           string               `yaml:"sort-by"`
+	Style          string               `yaml:"style"`
+	Markets        feed.Markets         `yaml:"-"`
 }
 
-func (widget *Stocks) Initialize() error {
-	widget.withTitle("Stocks").withCacheDuration(time.Hour)
+func (widget *Markets) Initialize() error {
+	widget.withTitle("Markets").withCacheDuration(time.Hour)
+
+	if len(widget.MarketRequests) == 0 {
+		widget.MarketRequests = widget.StocksRequests
+	}
 
 	return nil
 }
 
-func (widget *Stocks) Update(ctx context.Context) {
-	stocks, err := feed.FetchStocksDataFromYahoo(widget.Stocks)
+func (widget *Markets) Update(ctx context.Context) {
+	markets, err := feed.FetchMarketsDataFromYahoo(widget.MarketRequests)
 
 	if !widget.canContinueUpdateAfterHandlingErr(err) {
 		return
 	}
 
 	if widget.Sort == "absolute-change" {
-		stocks.SortByAbsChange()
+		markets.SortByAbsChange()
 	}
 
-	widget.Stocks = stocks
+	widget.Markets = markets
 }
 
-func (widget *Stocks) Render() template.HTML {
-	return widget.render(widget, assets.StocksTemplate)
+func (widget *Markets) Render() template.HTML {
+	return widget.render(widget, assets.MarketsTemplate)
 }

+ 2 - 2
internal/widget/widget.go

@@ -33,8 +33,8 @@ func New(widgetType string) (Widget, error) {
 		return &Releases{}, nil
 	case "videos":
 		return &Videos{}, nil
-	case "stocks":
-		return &Stocks{}, nil
+	case "markets", "stocks":
+		return &Markets{}, nil
 	case "reddit":
 		return &Reddit{}, nil
 	case "rss":