diff --git a/docs/docs/segment-strava.mdx b/docs/docs/segment-strava.mdx
new file mode 100644
index 00000000..1f3a07a6
--- /dev/null
+++ b/docs/docs/segment-strava.mdx
@@ -0,0 +1,90 @@
+---
+id: strava
+title: Strava
+sidebar_label: Strava
+---
+
+import StravaConnect from '/img/strava_connect.svg';
+
+## What
+
+[Strava][strava] ia a popular activity tracker for bike, run or any other traning.
+To keep up with your training goals it is important to be constantly reminded about it.
+A Strava Oh My Posh segment would show your last activity,
+and also indicate by a color if it is time to get away from your computer and do some workout.
+
+## Accessing your Strava data
+
+To allow Oh My Posh access your Strava data you need to grant access to read your public activities.
+This will give you an access and a refresh token. Paste the tokens into your strava segment configuration.
+
+Click the following link to connect with Strava:
+
+
+
+
+
+## Sample Configuration
+
+This configuration sets the background green if you have an activity the last two days,
+orange if you have one last 5 days, and red else. The `foreground_templates` example below could be set to just a single color,
+if that color is visible against any of your backgrounds.
+
+```json
+{
+ "type": "strava",
+ "style": "powerline",
+ "powerline_symbol": "\uE0B0",
+ "foreground": "#ffffff",
+ "background": "#000000",
+ "background_templates": [
+ "{{ if gt .Hours 100 }}#dc3545{{ end }}",
+ "{{ if and (lt .Hours 100) (gt .Hours 50) }}#ffc107{{ end }}",
+ "{{ if lt .Hours 50 }}#28a745{{ end }}"
+ ],
+ "foreground_templates": [
+ "{{ if gt .Hours 100 }}#FFFFFF{{ end }}",
+ "{{ if and (lt .Hours 100) (gt .Hours 50) }}#343a40{{ end }}",
+ "{{ if lt .Hours 50 }}#FFFFFF{{ end }}"
+ ],
+ "properties": {
+ "access_token":"11111111111111111",
+ "refresh_token":"1111111111111111",
+ "http_timeout": 1500,
+ "template": "{{.Name}} {{.Ago}} {{.ActivityIcon}}"
+ }
+}
+```
+
+## Properties
+
+- access_token: `string` - token from Strava login, see login link in section above. It has the following format: `1111111111111111111111111`
+- refresh_token: `string` - token from Strava login, see login link in section above. It has the following format: `1111111111111111111111111`
+- http_timeout: `int` - how long do you want to wait before you want to see your prompt more than your strava data? - defaults to 500ms
+- template: `string` - a go [text/template][go-text-template] template extended with [sprig][sprig] utilizing the properties below.
+See the example above. Make sure your NerdFont has the glyph you want or search for one at nerdfonts.com
+- CacheTimeout: `int` in minutes - How long do you want your numbers cached? - defaults to 5 min
+- RideIcon - defaults to `\uf5a2`
+- RunIcon - defaults to `\ufc0c`
+- SkiingIcon - defaults to `\ue213`
+- WorkOutIcon - defaults to `\ue213`
+- UnknownActivityIcon - defaults to `\ue213`
+
+## Template Properties
+
+The properties below are availible for use in your template
+
+- `.DateString`: `time` - The timestamp of the entry
+- `.Type`: `string` - Activity types as used in strava
+- `.UtcOffset`: `int` - The UTC offset
+- `.Hours`: `int` - Number of hours since last activity
+- `.Name`: `string` - The name of the activity
+- `.Duration`: `float64` - Total duration in seconds
+- `.Distance`: `float64` - Toatal distance in meters
+
+Now, go out and have a fun ride or run!
+
+[go-text-template]: https://golang.org/pkg/text/template/
+[sprig]: https://masterminds.github.io/sprig/
+[strava]: http://www.strava.com/
+[strava-connect]: https://www.strava.com/oauth/authorize?client_id=76033&response_type=code&redirect_uri=https://ohmyposh.dev/api/auth&approval_prompt=force&scope=read,activity:read
diff --git a/docs/sidebars.js b/docs/sidebars.js
index 4f980f42..95b0d003 100644
--- a/docs/sidebars.js
+++ b/docs/sidebars.js
@@ -70,6 +70,7 @@ module.exports = {
"session",
"shell",
"spotify",
+ "strava",
"sysinfo",
"terraform",
"text",
diff --git a/docs/static/img/strava_connect.svg b/docs/static/img/strava_connect.svg
new file mode 100644
index 00000000..04f0e813
--- /dev/null
+++ b/docs/static/img/strava_connect.svg
@@ -0,0 +1,14 @@
+
+
\ No newline at end of file
diff --git a/src/properties.go b/src/properties.go
index 4a56277f..dc0dab54 100644
--- a/src/properties.go
+++ b/src/properties.go
@@ -35,6 +35,10 @@ const (
DisplayError Property = "display_error"
// DisplayDefault hides or shows the default
DisplayDefault Property = "display_default"
+ // AccessToken is the access token to use for an API
+ AccessToken Property = "access_token"
+ // RefreshToken is the refresh token to use for an API
+ RefreshToken Property = "refresh_token"
)
type properties map[Property]interface{}
diff --git a/src/segment.go b/src/segment.go
index 12d18fcf..8a57f8fd 100644
--- a/src/segment.go
+++ b/src/segment.go
@@ -148,6 +148,8 @@ const (
PHP SegmentType = "php"
// Nightscout is an open source diabetes system
Nightscout SegmentType = "nightscout"
+ // Strava is a sports activity tracker
+ Strava SegmentType = "strava"
// Wakatime writes tracked time spend in dev editors
Wakatime SegmentType = "wakatime"
// WiFi writes details about the current WiFi connection
@@ -287,6 +289,7 @@ func (segment *Segment) mapSegmentWithWriter(env Environment) error {
Angular: &angular{},
PHP: &php{},
Nightscout: &nightscout{},
+ Strava: &strava{},
Wakatime: &wakatime{},
WiFi: &wifi{},
WinReg: &winreg{},
diff --git a/src/segment_strava.go b/src/segment_strava.go
new file mode 100644
index 00000000..2bb64c4f
--- /dev/null
+++ b/src/segment_strava.go
@@ -0,0 +1,237 @@
+package main
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "math"
+ "net/http"
+ "time"
+)
+
+// segment struct, makes templating easier
+type strava struct {
+ props Properties
+ env Environment
+
+ StravaData
+ ActivityIcon string
+ Ago string
+ Hours int
+ Authenticate bool
+ Error string
+}
+
+const (
+ RideIcon Property = "ride_icon"
+ RunIcon Property = "run_icon"
+ SkiingIcon Property = "skiing_icon"
+ WorkOutIcon Property = "workout_icon"
+ UnknownActivityIcon Property = "unknown_activity_icon"
+
+ StravaAccessToken = "strava_access_token"
+ StravaRefreshToken = "strava_refresh_token"
+
+ Timeout = "timeout"
+ InvalidRefreshToken = "invalid refresh token"
+ TokenRefreshFailed = "token refresh error"
+)
+
+// StravaData struct contains the API data
+type StravaData struct {
+ Type string `json:"type"`
+ StartDate time.Time `json:"start_date"`
+ Name string `json:"name"`
+ Distance float64 `json:"distance"`
+ Duration float64 `json:"moving_time"`
+}
+
+type TokenExchange struct {
+ AccessToken string `json:"access_token"`
+ RefreshToken string `json:"refresh_token"`
+ ExpiresIn int `json:"expires_in"`
+}
+
+type AuthError struct {
+ message string
+}
+
+func (a *AuthError) Error() string {
+ return a.message
+}
+
+func (s *strava) enabled() bool {
+ data, err := s.getResult()
+ if err == nil {
+ s.StravaData = *data
+ s.ActivityIcon = s.getActivityIcon()
+ s.Hours = s.getHours()
+ s.Ago = s.getAgo()
+ return true
+ }
+ if _, s.Authenticate = err.(*AuthError); s.Authenticate {
+ s.Error = err.(*AuthError).Error()
+ return true
+ }
+ return false
+}
+
+func (s *strava) getHours() int {
+ hours := time.Since(s.StartDate).Hours()
+ return int(math.Floor(hours))
+}
+
+func (s *strava) getAgo() string {
+ if s.Hours > 24 {
+ days := int32(math.Floor(float64(s.Hours) / float64(24)))
+ return fmt.Sprintf("%d", days) + string('d')
+ }
+ return fmt.Sprintf("%d", s.Hours) + string("h")
+}
+
+func (s *strava) getActivityIcon() string {
+ switch s.Type {
+ case "Ride":
+ return s.props.getString(RideIcon, "\uf5a2")
+ case "Run":
+ return s.props.getString(RunIcon, "\ufc0c")
+ case "NordicSki":
+ case "AlpineSki":
+ case "BackcountrySki":
+ return s.props.getString(SkiingIcon, "\ue213")
+ case "WorkOut":
+ return s.props.getString(WorkOutIcon, "\ue213")
+ default:
+ return s.props.getString(UnknownActivityIcon, "\ue213")
+ }
+ return s.props.getString(UnknownActivityIcon, "\ue213")
+}
+
+func (s *strava) string() string {
+ if s.Error != "" {
+ return s.Error
+ }
+ segmentTemplate := s.props.getString(SegmentTemplate, "{{ .Ago }}")
+ template := &textTemplate{
+ Template: segmentTemplate,
+ Context: s,
+ Env: s.env,
+ }
+ text, err := template.render()
+ if err != nil {
+ return err.Error()
+ }
+ return text
+}
+
+func (s *strava) getAccessToken() (string, error) {
+ // get directly from cache
+ if acccessToken, OK := s.env.cache().get(StravaAccessToken); OK {
+ return acccessToken, nil
+ }
+ // use cached refersh token to get new access token
+ if refreshToken, OK := s.env.cache().get(StravaRefreshToken); OK {
+ if acccessToken, err := s.refreshToken(refreshToken); err == nil {
+ return acccessToken, nil
+ }
+ }
+ // use initial refresh token from property
+ refreshToken := s.props.getString(RefreshToken, "")
+ if len(refreshToken) == 0 {
+ return "", &AuthError{
+ message: InvalidRefreshToken,
+ }
+ }
+ // no need to let the user provide access token, we'll always verify the refresh token
+ acccessToken, err := s.refreshToken(refreshToken)
+ return acccessToken, err
+}
+
+func (s *strava) refreshToken(refreshToken string) (string, error) {
+ httpTimeout := s.props.getInt(HTTPTimeout, DefaultHTTPTimeout)
+ url := fmt.Sprintf("https://ohmyposh.dev/api/refresh?segment=strava&token=%s", refreshToken)
+ body, err := s.env.HTTPRequest(url, httpTimeout)
+ if err != nil {
+ return "", &AuthError{
+ // This might happen if /api was asleep. Assume the user will just retry
+ message: Timeout,
+ }
+ }
+ tokens := &TokenExchange{}
+ err = json.Unmarshal(body, &tokens)
+ if err != nil {
+ return "", &AuthError{
+ message: TokenRefreshFailed,
+ }
+ }
+ // add tokens to cache
+ s.env.cache().set(StravaAccessToken, tokens.AccessToken, tokens.ExpiresIn/60)
+ s.env.cache().set(StravaRefreshToken, tokens.RefreshToken, 2*525960) // it should never expire unless revoked, default to 2 year
+ return tokens.AccessToken, nil
+}
+
+func (s *strava) getResult() (*StravaData, error) {
+ parseSingleElement := func(data []byte) (*StravaData, error) {
+ var result []*StravaData
+ err := json.Unmarshal(data, &result)
+ if err != nil {
+ return nil, err
+ }
+ if len(result) == 0 {
+ return nil, errors.New("no elements in the array")
+ }
+ return result[0], nil
+ }
+ getCacheValue := func(key string) (*StravaData, error) {
+ val, found := s.env.cache().get(key)
+ // we got something from the cache
+ if found {
+ if data, err := parseSingleElement([]byte(val)); err == nil {
+ return data, nil
+ }
+ }
+ return nil, errors.New("no data in cache")
+ }
+
+ // We only want the last activity
+ url := "https://www.strava.com/api/v3/athlete/activities?page=1&per_page=1"
+ httpTimeout := s.props.getInt(HTTPTimeout, DefaultHTTPTimeout)
+
+ // No need to check more the every 30 min
+ cacheTimeout := s.props.getInt(CacheTimeout, 30)
+ if cacheTimeout > 0 {
+ if data, err := getCacheValue(url); err == nil {
+ return data, nil
+ }
+ }
+ accessToken, err := s.getAccessToken()
+ if err != nil {
+ return nil, err
+ }
+ addAuthHeader := func(request *http.Request) {
+ request.Header.Add("Authorization", "Bearer "+accessToken)
+ }
+ body, err := s.env.HTTPRequest(url, httpTimeout, addAuthHeader)
+ if err != nil {
+ return nil, err
+ }
+ var arr []*StravaData
+ err = json.Unmarshal(body, &arr)
+ if err != nil {
+ return nil, err
+ }
+ data, err := parseSingleElement(body)
+ if err != nil {
+ return nil, err
+ }
+ if cacheTimeout > 0 {
+ // persist new sugars in cache
+ s.env.cache().set(url, string(body), cacheTimeout)
+ }
+ return data, nil
+}
+
+func (s *strava) init(props Properties, env Environment) {
+ s.props = props
+ s.env = env
+}
diff --git a/src/segment_strava_test.go b/src/segment_strava_test.go
new file mode 100644
index 00000000..1a46bd52
--- /dev/null
+++ b/src/segment_strava_test.go
@@ -0,0 +1,198 @@
+package main
+
+import (
+ "errors"
+ "fmt"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestStravaSegment(t *testing.T) {
+ h, _ := time.ParseDuration("6h")
+ sixHoursAgo := time.Now().Add(-h).Format(time.RFC3339)
+ h, _ = time.ParseDuration("100h")
+ fourDaysAgo := time.Now().Add(-h).Format(time.RFC3339)
+
+ cases := []struct {
+ Case string
+ JSONResponse string
+ AccessToken string
+ RefreshToken string
+ AccessTokenCacheFoundFail bool
+ RefreshTokenCacheFoundFail bool
+ InitialAccessToken string
+ InitialRefreshToken string
+ TokenRefreshToken string
+ TokenResponse string
+ TokenTest bool
+ ExpectedString string
+ ExpectedEnabled bool
+ CacheTimeout int
+ CacheFoundFail bool
+ Template string
+ Error error
+ AuthDebugMsg string
+ }{
+ {
+ Case: "No initial tokens",
+ InitialAccessToken: "",
+ AccessTokenCacheFoundFail: true,
+ RefreshTokenCacheFoundFail: true,
+ TokenTest: true,
+ AuthDebugMsg: "invalid refresh token",
+ },
+ {
+ Case: "Use initial tokens",
+ AccessToken: "NEW_ACCESSTOKEN",
+ InitialAccessToken: "INITIAL ACCESSTOKEN",
+ InitialRefreshToken: "INITIAL REFRESHTOKEN",
+ TokenRefreshToken: "INITIAL REFRESHTOKEN",
+ TokenResponse: `{ "access_token":"NEW_ACCESSTOKEN","refresh_token":"NEW_REFRESHTOKEN", "expires_in":1234 }`,
+ AccessTokenCacheFoundFail: true,
+ RefreshTokenCacheFoundFail: true,
+ TokenTest: true,
+ },
+ {
+ Case: "Access token from cache",
+ AccessToken: "ACCESSTOKEN",
+ TokenTest: true,
+ },
+ {
+ Case: "Refresh token from cache",
+ AccessTokenCacheFoundFail: true,
+ RefreshTokenCacheFoundFail: false,
+ RefreshToken: "REFRESHTOKEN",
+ TokenRefreshToken: "REFRESHTOKEN",
+ TokenTest: true,
+ AuthDebugMsg: "invalid refresh token",
+ },
+ {
+ Case: "Ride 6",
+ JSONResponse: `
+ [{"type":"Ride","start_date":"` + sixHoursAgo + `","name":"Sesongens første på tjukkas","distance":16144.0}]`,
+ Template: "{{.Ago}} {{.ActivityIcon}}",
+ ExpectedString: "6h \uf5a2",
+ ExpectedEnabled: true,
+ },
+ {
+ Case: "Run 100",
+ JSONResponse: `
+ [{"type":"Run","start_date":"` + fourDaysAgo + `","name":"Sesongens første på tjukkas","distance":16144.0,"moving_time":7665}]`,
+ Template: "{{.Ago}} {{.ActivityIcon}}",
+ ExpectedString: "4d \ufc0c",
+ ExpectedEnabled: true,
+ },
+ {
+ Case: "Error in retrieving data",
+ JSONResponse: "nonsense",
+ Error: errors.New("Something went wrong"),
+ ExpectedEnabled: false,
+ },
+ {
+ Case: "Empty array",
+ JSONResponse: "[]",
+ ExpectedEnabled: false,
+ },
+ {
+ Case: "Run from cache",
+ JSONResponse: `
+ [{"type":"Run","start_date":"` + fourDaysAgo + `","name":"Sesongens første på tjukkas","distance":16144.0,"moving_time":7665}]`,
+ Template: "{{.Ago}} {{.ActivityIcon}}",
+ ExpectedString: "4d \ufc0c",
+ ExpectedEnabled: true,
+ CacheTimeout: 10,
+ },
+ {
+ Case: "Run from not found cache",
+ JSONResponse: `
+ [{"type":"Run","start_date":"` + fourDaysAgo + `","name":"Morning ride","distance":16144.0,"moving_time":7665}]`,
+ Template: "{{.Ago}} {{.ActivityIcon}} {{.Name}} {{.Hours}}h ago",
+ ExpectedString: "4d \ufc0c Morning ride 100h ago",
+ ExpectedEnabled: true,
+ CacheTimeout: 10,
+ CacheFoundFail: true,
+ },
+ {
+ Case: "Error parsing response",
+ JSONResponse: `
+ 4tffgt4e4567`,
+ Template: "{{.Ago}}{{.ActivityIcon}}",
+ ExpectedString: "50",
+ ExpectedEnabled: false,
+ CacheTimeout: 10,
+ },
+ {
+ Case: "Faulty template",
+ JSONResponse: `
+ [{"sgv":50,"direction":"DoubleDown"}]`,
+ Template: "{{.Ago}}{{.Burp}}",
+ ExpectedString: incorrectTemplate,
+ ExpectedEnabled: true,
+ CacheTimeout: 10,
+ },
+ }
+
+ for _, tc := range cases {
+ env := &MockedEnvironment{}
+ url := "https://www.strava.com/api/v3/athlete/activities?page=1&per_page=1"
+ tokenURL := fmt.Sprintf("https://ohmyposh.dev/api/refresh?segment=strava&token=%s", tc.TokenRefreshToken)
+ var props properties = map[Property]interface{}{
+ CacheTimeout: tc.CacheTimeout,
+ }
+ cache := &MockedCache{}
+ cache.On("get", url).Return(tc.JSONResponse, !tc.CacheFoundFail)
+ cache.On("set", url, tc.JSONResponse, tc.CacheTimeout).Return()
+
+ cache.On("get", StravaAccessToken).Return(tc.AccessToken, !tc.AccessTokenCacheFoundFail)
+ cache.On("get", StravaRefreshToken).Return(tc.RefreshToken, !tc.RefreshTokenCacheFoundFail)
+
+ cache.On("set", StravaRefreshToken, "NEW_REFRESHTOKEN", 2*525960)
+ cache.On("set", StravaAccessToken, "NEW_ACCESSTOKEN", 20)
+
+ env.On("HTTPRequest", url).Return([]byte(tc.JSONResponse), tc.Error)
+ env.On("HTTPRequest", tokenURL).Return([]byte(tc.TokenResponse), tc.Error)
+ env.On("cache", nil).Return(cache)
+
+ if tc.Template != "" {
+ props[SegmentTemplate] = tc.Template
+ }
+ if tc.InitialAccessToken != "" {
+ props[AccessToken] = tc.InitialAccessToken
+ }
+ if tc.InitialRefreshToken != "" {
+ props[RefreshToken] = tc.InitialRefreshToken
+ }
+
+ ns := &strava{
+ props: props,
+ env: env,
+ }
+
+ if tc.TokenTest {
+ // continue
+ at, err := ns.getAccessToken()
+ if err != nil {
+ if authErr, ok := err.(*AuthError); ok {
+ assert.Equal(t, tc.AuthDebugMsg, authErr.Error(), tc.Case)
+ } else {
+ assert.Equal(t, tc.Error, err, tc.Case)
+ }
+ } else {
+ assert.Equal(t, tc.AccessToken, at, tc.Case)
+ }
+ continue
+ }
+
+ enabled := ns.enabled()
+ assert.Equal(t, tc.ExpectedEnabled, enabled, tc.Case)
+ if !enabled {
+ continue
+ }
+
+ var a = ns.string()
+
+ assert.Equal(t, tc.ExpectedString, a, tc.Case)
+ }
+}
diff --git a/themes/larserikfinholt.omp.json b/themes/larserikfinholt.omp.json
new file mode 100644
index 00000000..85a31e04
--- /dev/null
+++ b/themes/larserikfinholt.omp.json
@@ -0,0 +1,112 @@
+{
+ "$schema": "https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json",
+ "blocks": [
+ {
+ "type": "prompt",
+ "alignment": "left",
+ "segments": [
+ {
+ "type": "session",
+ "style": "diamond",
+ "foreground": "#ffffff",
+ "background": "#c386f1",
+ "leading_diamond": "",
+ "trailing_diamond": "\uE0B0",
+ "properties": {
+ "template": "{{ .UserName }}"
+ }
+ },
+ {
+ "type": "path",
+ "style": "powerline",
+ "powerline_symbol": "\uE0B0",
+ "foreground": "#ffffff",
+ "background": "#ff479c",
+ "properties": {
+ "prefix": " ",
+ "home_icon": "~",
+ "folder_separator_icon": " \uE0b1 ",
+ "style": "folder"
+ }
+ },
+ {
+ "type": "git",
+ "style": "powerline",
+ "powerline_symbol": "\uE0B0",
+ "foreground": "#193549",
+ "background": "#fffb38",
+ "background_templates": [
+ "{{ if or (.Working.Changed) (.Staging.Changed) }}#FF9248{{ end }}",
+ "{{ if and (gt .Ahead 0) (gt .Behind 0) }}#ff4500{{ end }}",
+ "{{ if gt .Ahead 0 }}#B388FF{{ end }}",
+ "{{ if gt .Behind 0 }}#B388FF{{ end }}"
+ ],
+ "leading_diamond": "",
+ "trailing_diamond": "",
+ "properties": {
+ "fetch_status": true,
+ "fetch_stash_count": true,
+ "fetch_upstream_icon": true,
+ "branch_max_length": 25,
+ "template": "{{ .UpstreamIcon }}{{ .HEAD }}{{ .BranchStatus }}{{ if .Working.Changed }} \uF044 {{ .Working.String }}{{ end }}{{ if and (.Working.Changed) (.Staging.Changed) }} |{{ end }}{{ if .Staging.Changed }} \uF046 {{ .Staging.String }}{{ end }}{{ if gt .StashCount 0 }} \uF692 {{ .StashCount }}{{ end }}"
+ }
+ },
+ {
+ "type": "executiontime",
+ "style": "plain",
+ "foreground": "#ffffff",
+ "background": "#83769c",
+ "leading_diamond": "",
+ "trailing_diamond": "",
+ "properties": {
+ "always_enabled": true,
+ "prefix": "\uE0B0> \ufbab",
+ "postfix": "\u2800"
+ }
+ },
+ {
+ "type": "strava",
+ "style": "powerline",
+ "foreground": "#ffffff",
+ "background": "#000000",
+ "background_templates": [
+ "{{ if gt .Hours 100 }}#dc3545{{ end }}",
+ "{{ if and (lt .Hours 100) (gt .Hours 50) }}#ffc107{{ end }}",
+ "{{ if lt .Hours 50 }}#28a745{{ end }}"
+ ],
+ "foreground_templates": [
+ "{{ if gt .Hours 100 }}#FFFFFF{{ end }}",
+ "{{ if and (lt .Hours 100) (gt .Hours 50) }}#343a40{{ end }}",
+ "{{ if lt .Hours 50 }}#FFFFFF{{ end }}"
+ ],
+ "leading_diamond": "",
+ "trailing_diamond": "",
+ "properties": {
+ "access_token": "0ccbd2ac1e37a5b84101468df3d367177fe02ab3",
+ "refresh_token": "111111111111111111111111111111",
+ "http_timeout": 1500,
+ "template": "{{.Name}} {{.Ago}} {{.ActivityIcon}}"
+ }
+ },
+ {
+ "type": "exit",
+ "style": "diamond",
+ "foreground": "#ffffff",
+ "background": "#00897b",
+ "background_templates": ["{{ if gt .Code 0 }}#e91e63{{ end }}"],
+ "leading_diamond": "",
+ "trailing_diamond": "\uE0B4",
+ "properties": {
+ "always_enabled": true,
+ "template": "\uE23A",
+ "prefix": "\uE0B0> "
+ }
+ }
+ ]
+ }
+ ],
+ "final_space": true,
+ "console_title": true,
+ "console_title_style": "template",
+ "console_title_template": "{{ .Shell }} in {{ .Folder }}"
+}
diff --git a/themes/schema.json b/themes/schema.json
index c0f8ad10..a4df40c1 100644
--- a/themes/schema.json
+++ b/themes/schema.json
@@ -178,6 +178,7 @@
"rust",
"owm",
"sysinfo",
+ "strava",
"angular",
"php",
"wakatime",
@@ -1604,6 +1605,71 @@
}
}
},
+ {
+ "if": {
+ "properties": {
+ "type": { "const": "strava" }
+ }
+ },
+ "then": {
+ "title": "Display training data from Strava",
+ "description": "https://ohmyposh.dev/docs/strava",
+ "properties": {
+ "properties": {
+ "properties": {
+ "url": {
+ "type": "string",
+ "title": "URL of API with Strava data",
+ "description": "Url of your api provinding a Strava activity",
+ "default": ""
+ },
+ "ride_icon": {
+ "type": "string",
+ "title": "Alternative icon",
+ "description": "Alternative icon for this activity type",
+ "default": "\uf5a2"
+ },
+ "run_icon": {
+ "type": "string",
+ "title": "Alternative icon",
+ "description": "Alternative icon for this activity type",
+ "default": "\ufc0c"
+ },
+ "skiing_icon": {
+ "type": "string",
+ "title": "Alternative icon",
+ "description": "Alternative icon for this activity type",
+ "default": "\ue213"
+ },
+ "workout_icon": {
+ "type": "string",
+ "title": "Alternative icon",
+ "description": "Alternative icon for this activity type",
+ "default": "\ue213"
+ },
+ "unknown_activity_icon": {
+ "type": "string",
+ "title": "Fallback icon",
+ "description": "Fallback icon for other activity types",
+ "default": "\ue213"
+ },
+ "http_timeout": {
+ "$ref": "#/definitions/http_timeout"
+ },
+ "cache_timeout": {
+ "type": "integer",
+ "title": "cache timeout",
+ "description": "The number of minutes the response is cached. A value of 0 disables the cache.",
+ "default": 10
+ },
+ "template": {
+ "$ref": "#/definitions/template"
+ }
+ }
+ }
+ }
+ }
+ },
{
"if": {
"properties": {