mirror of
https://github.com/glanceapp/glance.git
synced 2024-11-22 00:13:55 +01:00
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
This commit is contained in:
parent
d2f5dbbc26
commit
1bebb88d0e
@ -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.
|
||||
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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>
|
@ -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)
|
||||
})
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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":
|
||||
|
Loading…
Reference in New Issue
Block a user