feat: Refine trail category model (#1059)
* feat: advanced trail categories * rename remote_category * fix merge issues * subcategories for plugins mapping * fix refresh * remove vertical trail filter category scrolling * cleanup * add confirm modal for disabling a category * Fix federation issues * fix review findings * redesign category settings page * remove prio badge * fix subcategory badge layout * optimize subcategory settings layout * further settings page layout optimization * fix subcategory icon position * fix * update docs --------- Co-authored-by: Christian Beutel <>
This commit is contained in:
@@ -204,10 +204,24 @@ Manifest `configSchema` defines plugin-owned settings that are passed to plugin
|
||||
| `merge.available` | Controls whether the UI offers auto-merge for this plugin. Defaults to `true`. |
|
||||
| `merge.enabled` | Runs auto-merge after trail import. |
|
||||
| `createSummitLogForCompleted` | Creates summit logs for completed imports. |
|
||||
| `categoryMapping` | Maps `metadata.providerCategory` to local category IDs or names. |
|
||||
| `categoryMapping` | Maps `metadata.providerCategory` to local category or subcategory targets. |
|
||||
| `connectors` | Provides host-owned base URL, TLS, private-network, and storage redirect settings for configured connectors. |
|
||||
|
||||
The settings UI lets users edit `categoryMapping` per plugin instance for trail import plugins.
|
||||
Mapping values can be strings for broad category-only compatibility, or objects
|
||||
with `category` and optional `subcategory`, using local record IDs or canonical
|
||||
names:
|
||||
|
||||
```json
|
||||
{
|
||||
"categoryMapping": {
|
||||
"Ride": { "category": "Biking", "subcategory": "Road" },
|
||||
"GravelRide": { "category": "Biking", "subcategory": "Gravel" },
|
||||
"Hike": "Hiking"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Plugins may describe provider-owned category values for the settings UI with
|
||||
`metadata.providerCategories`. This is display-only metadata; `categoryMapping`
|
||||
keys still use the raw provider category values emitted as
|
||||
|
||||
@@ -101,31 +101,31 @@
|
||||
"hostConfig": {
|
||||
"categoryMapping": {
|
||||
"hike": "Hiking",
|
||||
"mountaineering": "Hiking",
|
||||
"racebike": "Biking",
|
||||
"e_racebike": "Biking",
|
||||
"touringbicycle": "Biking",
|
||||
"e_touringbicycle": "Biking",
|
||||
"mtb": "Biking",
|
||||
"e_mtb": "Biking",
|
||||
"mtb_easy": "Biking",
|
||||
"e_mtb_easy": "Biking",
|
||||
"mtb_advanced": "Biking",
|
||||
"e_mtb_advanced": "Biking",
|
||||
"downhillbike": "Biking",
|
||||
"mountaineering": { "category": "Hiking", "subcategory": "Alpine" },
|
||||
"racebike": { "category": "Biking", "subcategory": "Road" },
|
||||
"e_racebike": { "category": "Biking", "subcategory": "E-Bike" },
|
||||
"touringbicycle": { "category": "Biking", "subcategory": "Touring" },
|
||||
"e_touringbicycle": { "category": "Biking", "subcategory": "Touring" },
|
||||
"mtb": { "category": "Biking", "subcategory": "MTB" },
|
||||
"e_mtb": { "category": "Biking", "subcategory": "MTB" },
|
||||
"mtb_easy": { "category": "Biking", "subcategory": "MTB" },
|
||||
"e_mtb_easy": { "category": "Biking", "subcategory": "MTB" },
|
||||
"mtb_advanced": { "category": "Biking", "subcategory": "MTB" },
|
||||
"e_mtb_advanced": { "category": "Biking", "subcategory": "MTB" },
|
||||
"downhillbike": { "category": "Biking", "subcategory": "MTB" },
|
||||
"unicycle": "Biking",
|
||||
"citybike": "Biking",
|
||||
"jogging": "Walking",
|
||||
"citybike": { "category": "Biking", "subcategory": "Touring" },
|
||||
"jogging": { "category": "Running", "subcategory": "Road" },
|
||||
"nordicwalking": "Walking",
|
||||
"skaten": "Walking",
|
||||
"other": "Walking",
|
||||
"climbing": "Climbing",
|
||||
"nordic": "Skiing",
|
||||
"nordic": { "category": "Skiing", "subcategory": "Cross-country" },
|
||||
"skialpin": "Skiing",
|
||||
"skitour": "Skiing",
|
||||
"skitour": { "category": "Skiing", "subcategory": "Backcountry" },
|
||||
"sled": "Skiing",
|
||||
"snowboard": "Skiing",
|
||||
"snowshoe": "Skiing"
|
||||
"snowshoe": { "category": "Hiking", "subcategory": "Snowshoeing" }
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
|
||||
@@ -412,7 +412,17 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"categoryMapping": {
|
||||
"$ref": "#/definitions/stringMap"
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/categoryMappingTarget"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"connectors": {
|
||||
"type": "object",
|
||||
@@ -476,6 +486,18 @@
|
||||
"$ref": "#/definitions/stringMap"
|
||||
}
|
||||
}
|
||||
},
|
||||
"categoryMappingTarget": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"category": {
|
||||
"type": "string"
|
||||
},
|
||||
"subcategory": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
22
plugins/strava/activity_type.go
Normal file
22
plugins/strava/activity_type.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
func activityTypeFromProvider(value string) string {
|
||||
switch value {
|
||||
case "AlpineSki", "BackcountrySki", "IceSkate", "NordicSki", "RollerSki", "Snowboard":
|
||||
return "skiing"
|
||||
case "Canoeing", "Kayaking", "Kitesurf", "Rowing", "Sail", "StandUpPaddling", "Surfing", "Windsurf":
|
||||
return "canoeing"
|
||||
case "Hike", "Snowshoe":
|
||||
return "hiking"
|
||||
case "Run", "TrailRun", "VirtualRun":
|
||||
return "running"
|
||||
case "Walk", "Golf", "Skateboard", "Wheelchair":
|
||||
return "walking"
|
||||
case "Ride", "EBikeRide", "Handcycle", "InlineSkate", "Velomobile", "VirtualRide":
|
||||
return "biking"
|
||||
case "RockClimbing":
|
||||
return "climbing"
|
||||
default:
|
||||
return value
|
||||
}
|
||||
}
|
||||
@@ -208,21 +208,5 @@ func providerActivityType(activity *detailedActivity) string {
|
||||
}
|
||||
|
||||
func activityType(activity *detailedActivity) string {
|
||||
value := providerActivityType(activity)
|
||||
switch value {
|
||||
case "AlpineSki", "BackcountrySki", "IceSkate", "NordicSki", "RollerSki", "Snowboard":
|
||||
return "skiing"
|
||||
case "Canoeing", "Kayaking", "Kitesurf", "Rowing", "Sail", "StandUpPaddling", "Surfing", "Windsurf":
|
||||
return "canoeing"
|
||||
case "Hike", "Snowshoe":
|
||||
return "hiking"
|
||||
case "Run", "VirtualRun", "Walk", "Golf", "Skateboard", "Wheelchair":
|
||||
return "walking"
|
||||
case "Ride", "EBikeRide", "Handcycle", "InlineSkate", "Velomobile", "VirtualRide":
|
||||
return "biking"
|
||||
case "RockClimbing":
|
||||
return "climbing"
|
||||
default:
|
||||
return value
|
||||
}
|
||||
return activityTypeFromProvider(providerActivityType(activity))
|
||||
}
|
||||
|
||||
18
plugins/strava/mapper_test.go
Normal file
18
plugins/strava/mapper_test.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestActivityTypeMapsRunsToRunning(t *testing.T) {
|
||||
cases := map[string]string{
|
||||
"Run": "running",
|
||||
"TrailRun": "running",
|
||||
"VirtualRun": "running",
|
||||
"Walk": "walking",
|
||||
}
|
||||
|
||||
for providerType, want := range cases {
|
||||
if got := activityTypeFromProvider(providerType); got != want {
|
||||
t.Fatalf("activityTypeFromProvider(%q) = %q, want %q", providerType, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,15 +145,15 @@
|
||||
"route:1": "Biking",
|
||||
"route:2": "Walking",
|
||||
"AlpineSki": "Skiing",
|
||||
"BackcountrySki": "Skiing",
|
||||
"BackcountrySki": { "category": "Skiing", "subcategory": "Backcountry" },
|
||||
"Badminton": "Other",
|
||||
"Canoeing": "Canoeing",
|
||||
"Crossfit": "Workout",
|
||||
"EBikeRide": "Biking",
|
||||
"EMountainBikeRide": "Biking",
|
||||
"Elliptical": "Workout",
|
||||
"Crossfit": "Other",
|
||||
"EBikeRide": { "category": "Biking", "subcategory": "E-Bike" },
|
||||
"EMountainBikeRide": { "category": "Biking", "subcategory": "E-Bike" },
|
||||
"Elliptical": "Other",
|
||||
"Golf": "Other",
|
||||
"GravelRide": "Biking",
|
||||
"GravelRide": { "category": "Biking", "subcategory": "Gravel" },
|
||||
"Handcycle": "Biking",
|
||||
"HighIntensityIntervalTraining": "Other",
|
||||
"Hike": "Hiking",
|
||||
@@ -161,40 +161,40 @@
|
||||
"InlineSkate": "Walking",
|
||||
"Kayaking": "Canoeing",
|
||||
"Kitesurf": "Canoeing",
|
||||
"MountainBikeRide": "Biking",
|
||||
"NordicSki": "Skiing",
|
||||
"MountainBikeRide": { "category": "Biking", "subcategory": "MTB" },
|
||||
"NordicSki": { "category": "Skiing", "subcategory": "Cross-country" },
|
||||
"Pickleball": "Other",
|
||||
"Pilates": "Other",
|
||||
"Racquetball": "Other",
|
||||
"Ride": "Biking",
|
||||
"Ride": { "category": "Biking", "subcategory": "Road" },
|
||||
"RockClimbing": "Climbing",
|
||||
"RollerSki": "Skiing",
|
||||
"RollerSki": { "category": "Skiing", "subcategory": "Cross-country" },
|
||||
"Rowing": "Canoeing",
|
||||
"Run": "Walking",
|
||||
"Run": { "category": "Running", "subcategory": "Road" },
|
||||
"Sail": "Canoeing",
|
||||
"Skateboard": "Walking",
|
||||
"Snowboard": "Skiing",
|
||||
"Snowshoe": "Hiking",
|
||||
"Snowshoe": { "category": "Hiking", "subcategory": "Snowshoeing" },
|
||||
"Soccer": "Other",
|
||||
"Squash": "Other",
|
||||
"StairStepper": "Workout",
|
||||
"StairStepper": "Other",
|
||||
"StandUpPaddling": "Canoeing",
|
||||
"Surfing": "Canoeing",
|
||||
"Swim": "Other",
|
||||
"TableTennis": "Other",
|
||||
"Tennis": "Other",
|
||||
"TrailRun": "Other",
|
||||
"TrailRun": { "category": "Running", "subcategory": "Trail" },
|
||||
"Training": "Other",
|
||||
"Velomobile": "Biking",
|
||||
"VirtualRide": "Biking",
|
||||
"VirtualRow": "Other",
|
||||
"VirtualRun": "Walking",
|
||||
"VirtualRun": "Running",
|
||||
"Walk": "Walking",
|
||||
"WeightTraining": "Workout",
|
||||
"WeightTraining": "Other",
|
||||
"Wheelchair": "Walking",
|
||||
"Windsurf": "Canoeing",
|
||||
"Workout": "Workout",
|
||||
"Yoga": "Workout"
|
||||
"Workout": "Other",
|
||||
"Yoga": "Other"
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
|
||||
Reference in New Issue
Block a user