Skip to content

Commit

Permalink
Merge pull request #174 from jovandeginste/non-location-wo
Browse files Browse the repository at this point in the history
Add other types of workouts without location
  • Loading branch information
jovandeginste committed Jul 1, 2024
2 parents 11a7bc6 + 9e4c8cd commit 4101e4a
Show file tree
Hide file tree
Showing 19 changed files with 431 additions and 90 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ build-dist: clean-dist
cp -R ./node_modules/@fortawesome/fontawesome-free/ ./assets/dist/fontawesome/
cp -v ./node_modules/apexcharts/dist/apexcharts.min.js ./assets/dist/
cp -v ./node_modules/apexcharts/dist/apexcharts.css ./assets/dist/
cp -v ./node_modules/htmx.org/dist/htmx.min.js ./assets/dist/


watch-tw:
Expand Down
4 changes: 4 additions & 0 deletions assets/output.css
Original file line number Diff line number Diff line change
Expand Up @@ -2824,6 +2824,10 @@ table {
content: "\f00d";
}

.\[a-zA-Z\:\\-\\\.\] {
a-z-a--z: \-\.;
}

@media (min-width: 640px) {
.sm\:mt-0 {
margin-top: 0px;
Expand Down
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"@fortawesome/fontawesome-free": "^6.5.1",
"apexcharts": "^3.48.0",
"fullcalendar": "^6.1.11",
"htmx.org": "^1.9.12",
"leaflet": "^1.9.4",
"shareon": "^2.5.0",
"sorttable": "^1.0.2",
Expand Down
1 change: 1 addition & 0 deletions pkg/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ func (a *App) Configure() error {
if err := a.ConfigureDatabase(); err != nil {
return err
}

if err := a.ConfigureGeocoder(); err != nil {
return err
}
Expand Down
1 change: 1 addition & 0 deletions pkg/app/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ func (a *App) secureRoutes(e *echo.Group) *echo.Group {
workoutsGroup.POST("/:id/delete", a.workoutsDeleteHandler).Name = "workout-delete"
workoutsGroup.POST("/:id/refresh", a.workoutsRefreshHandler).Name = "workout-refresh"
workoutsGroup.GET("/add", a.workoutsAddHandler).Name = "workout-add"
workoutsGroup.GET("/form", a.workoutsFormHandler).Name = "workout-form"

equipmentGroup := secureGroup.Group("/equipment")
equipmentGroup.GET("", a.equipmentHandler).Name = "equipment"
Expand Down
69 changes: 69 additions & 0 deletions pkg/app/workouts.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@ import (
"mime/multipart"
"net/http"
"strings"
"time"

"github.com/jovandeginste/workout-tracker/pkg/database"
"github.com/labstack/echo/v4"
)

const (
htmlDateFormat = "2006-01-02T15:04"
htmlDurationFormat = "15:04"
)

func uploadedFile(file *multipart.FileHeader) ([]byte, error) {
src, err := file.Open()
if err != nil {
Expand All @@ -26,7 +32,70 @@ func uploadedFile(file *multipart.FileHeader) ([]byte, error) {
return content, nil
}

type ManualWorkout struct {
Name string `form:"name"`
Date string `form:"date"`
Duration string `form:"duration"`
Repetitions int `form:"repetitions"`
Weight float64 `form:"weight"`
Notes string `form:"notes"`
Type database.WorkoutType `form:"type"`
}

func (m *ManualWorkout) ToDate() time.Time {
d, err := time.Parse(htmlDateFormat, m.Date)
if err != nil {
return time.Time{}
}

return d
}

func (m *ManualWorkout) ToDuration() time.Duration {
d, err := time.Parse(htmlDurationFormat, m.Duration)
if err != nil {
return 0
}

return time.Duration(d.Hour())*time.Hour + time.Duration(d.Minute())*time.Minute
}

func (a *App) addWorkout(c echo.Context) error {
if strings.HasPrefix(c.Request().Header.Get(echo.HeaderContentType), echo.MIMEMultipartForm) {
return a.addWorkoutFromFile(c)
}

d := &ManualWorkout{}

if err := c.Bind(d); err != nil {
return a.redirectWithError(c, "/workouts", err)
}

dDate := d.ToDate()
dDuration := d.ToDuration()

w := database.Workout{
Name: d.Name,
Notes: d.Notes,
Date: &dDate,
Type: d.Type,
User: a.getCurrentUser(c),
UserID: a.getCurrentUser(c).ID,
Data: &database.MapData{
TotalDuration: dDuration,
TotalRepetitions: d.Repetitions,
TotalWeight: d.Weight,
},
}

if err := w.Save(a.db); err != nil {
return a.redirectWithError(c, "/workouts", err)
}

return c.Redirect(http.StatusFound, a.echo.Reverse("workouts"))
}

func (a *App) addWorkoutFromFile(c echo.Context) error {
form, err := c.MultipartForm()
if err != nil {
return err
Expand Down
5 changes: 5 additions & 0 deletions pkg/app/workouts_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ func (a *App) workoutsAddHandler(c echo.Context) error {
return c.Render(http.StatusOK, "workouts_add.html", data)
}

func (a *App) workoutsFormHandler(c echo.Context) error {
t := database.WorkoutType(c.FormValue("type"))
return c.Render(http.StatusOK, "workout_form.html", t)
}

func (a *App) workoutsDeleteHandler(c echo.Context) error { //nolint:dupl
workout, err := a.getWorkout(c)
if err != nil {
Expand Down
9 changes: 7 additions & 2 deletions pkg/database/equipment.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,17 @@ func (e *Equipment) GetTotals() (WorkoutTotals, error) {
if w.Type.IsDuration() {
rs.Duration += w.Duration()
}

if w.Type.IsRepetition() {
rs.Repetitions += w.Repetitions()
}
}

return rs, nil
}

type WorkoutTotals struct {
Distance float64
Duration time.Duration
Distance float64
Duration time.Duration
Repetitions int
}
10 changes: 10 additions & 0 deletions pkg/database/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type UserPreferredUnits struct {
SpeedRaw string `form:"speed" json:"speed"` // The user's preferred speed unit
DistanceRaw string `form:"distance" json:"distance"` // The user's preferred distance unit
ElevationRaw string `form:"elevation" json:"elevation"` // The user's preferred elevation unit
WeightRaw string `form:"weight" json:"weight"` // The user's preferred weight unit
}

func (u UserPreferredUnits) Tempo() string {
Expand All @@ -50,6 +51,15 @@ func (u UserPreferredUnits) Elevation() string {
}
}

func (u UserPreferredUnits) Weight() string {
switch u.WeightRaw {
case "lbs":
return "lbs"
default:
return "kg"
}
}

func (u UserPreferredUnits) Distance() string {
switch u.DistanceRaw {
case "mi":
Expand Down
103 changes: 86 additions & 17 deletions pkg/database/workout_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,109 @@ const (
// We need to add each of these types to the "messages.html" partial view.
// Then it gets picked up by the i18n system, added to the list of translatable
// strings, etc.
WorkoutTypeAutoDetect WorkoutType = "auto"
WorkoutTypeRunning WorkoutType = "running"
WorkoutTypeCycling WorkoutType = "cycling"
WorkoutTypeWalking WorkoutType = "walking"
WorkoutTypeSkiing WorkoutType = "skiing"
WorkoutTypeSnowboarding WorkoutType = "snowboarding"
WorkoutTypeSwimming WorkoutType = "swimming"
WorkoutTypeKayaking WorkoutType = "kayaking"
WorkoutTypeGolfing WorkoutType = "golfing"
WorkoutTypeHiking WorkoutType = "hiking"
WorkoutTypeAutoDetect WorkoutType = "auto"
WorkoutTypeRunning WorkoutType = "running"
WorkoutTypeCycling WorkoutType = "cycling"
WorkoutTypeWalking WorkoutType = "walking"
WorkoutTypeSkiing WorkoutType = "skiing"
WorkoutTypeSnowboarding WorkoutType = "snowboarding"
WorkoutTypeSwimming WorkoutType = "swimming"
WorkoutTypeKayaking WorkoutType = "kayaking"
WorkoutTypeGolfing WorkoutType = "golfing"
WorkoutTypeHiking WorkoutType = "hiking"
WorkoutTypePushups WorkoutType = "push-ups"
WorkoutTypeWeightLifting WorkoutType = "weight lifting"
)

func WorkoutTypes() []WorkoutType {
return []WorkoutType{WorkoutTypeRunning, WorkoutTypeCycling, WorkoutTypeWalking, WorkoutTypeSkiing, WorkoutTypeSnowboarding, WorkoutTypeSwimming, WorkoutTypeKayaking, WorkoutTypeGolfing, WorkoutTypeHiking}
type WorkoutTypeConfiguration struct {
Location bool
Distance bool
Repetition bool
Weight bool
}

var workoutTypeConfigs = map[WorkoutType]WorkoutTypeConfiguration{
WorkoutTypeRunning: {Location: true, Distance: true, Repetition: false, Weight: false},
WorkoutTypeCycling: {Location: true, Distance: true, Repetition: false, Weight: false},
WorkoutTypeWalking: {Location: true, Distance: true, Repetition: false, Weight: false},
WorkoutTypeSkiing: {Location: true, Distance: true, Repetition: false, Weight: false},
WorkoutTypeSnowboarding: {Location: true, Distance: true, Repetition: false, Weight: false},
WorkoutTypeSwimming: {Location: true, Distance: true, Repetition: false, Weight: false},
WorkoutTypeKayaking: {Location: true, Distance: true, Repetition: false, Weight: false},
WorkoutTypeGolfing: {Location: true, Distance: true, Repetition: false, Weight: false},
WorkoutTypeHiking: {Location: true, Distance: true, Repetition: false, Weight: false},

WorkoutTypePushups: {Location: false, Distance: false, Repetition: true, Weight: false},
WorkoutTypeWeightLifting: {Location: false, Distance: false, Repetition: true, Weight: true},
}

func DurationWorkoutTypes() []WorkoutType {
return []WorkoutType{WorkoutTypeRunning, WorkoutTypeCycling, WorkoutTypeWalking, WorkoutTypeSkiing, WorkoutTypeSnowboarding, WorkoutTypeSwimming, WorkoutTypeKayaking, WorkoutTypeGolfing, WorkoutTypeHiking}
func WorkoutTypes() []WorkoutType {
keys := []WorkoutType{}

for k := range workoutTypeConfigs {
keys = append(keys, k)
}

slices.Sort(keys)

return keys
}

func DistanceWorkoutTypes() []WorkoutType {
return []WorkoutType{WorkoutTypeRunning, WorkoutTypeCycling, WorkoutTypeWalking, WorkoutTypeSkiing, WorkoutTypeSnowboarding, WorkoutTypeSwimming, WorkoutTypeKayaking, WorkoutTypeGolfing, WorkoutTypeHiking}
keys := []WorkoutType{}

for k, c := range workoutTypeConfigs {
if !c.Distance {
continue
}

keys = append(keys, k)
}

slices.Sort(keys)

return keys
}

func LocationWorkoutTypes() []WorkoutType {
keys := []WorkoutType{}

for k, c := range workoutTypeConfigs {
if !c.Location {
continue
}

keys = append(keys, k)
}

slices.Sort(keys)

return keys
}

func (wt WorkoutType) String() string {
return string(wt)
}

func (wt WorkoutType) IsDistance() bool {
return slices.Contains(DistanceWorkoutTypes(), wt)
return workoutTypeConfigs[wt].Distance
}

func (wt WorkoutType) IsRepetition() bool {
return workoutTypeConfigs[wt].Repetition
}

func (wt WorkoutType) IsDuration() bool {
return slices.Contains(DurationWorkoutTypes(), wt)
_, ok := workoutTypeConfigs[wt]
return ok
}

func (wt WorkoutType) IsWeight() bool {
return workoutTypeConfigs[wt].Weight
}

func (wt WorkoutType) IsLocation() bool {
return workoutTypeConfigs[wt].Location
}

func AsWorkoutType(s string) WorkoutType {
Expand Down
8 changes: 8 additions & 0 deletions pkg/database/workouts.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ type GPXData struct {
Filename string // The filename of the file
}

func (w *Workout) Repetitions() int {
if w.Data == nil {
return 0
}

return w.Data.TotalRepetitions
}

func (w *Workout) Duration() time.Duration {
if w.Data == nil {
return 0
Expand Down
30 changes: 16 additions & 14 deletions pkg/database/workouts_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,22 @@ func correctAltitude(creator string, lat, long, alt float64) float64 {

type MapData struct {
gorm.Model
WorkoutID uint `gorm:"not null;uniqueIndex"` // The workout this data belongs to
Creator string // The tool that created this workout
Name string // The name of the workout
Center MapCenter `gorm:"serializer:json"` // The center of the workout (in coordinates)
Address *geo.Address `gorm:"serializer:json"` // The address of the workout
TotalDistance float64 // The total distance of the workout
TotalDuration time.Duration // The total duration of the workout
MaxSpeed float64 // The maximum speed of the workout
PauseDuration time.Duration // The total pause duration of the workout
MinElevation float64 // The minimum elevation of the workout
MaxElevation float64 // The maximum elevation of the workout
TotalUp float64 // The total distance up of the workout
TotalDown float64 // The total distance down of the workout
Details *MapDataDetails `json:",omitempty"` // The details of the workout
WorkoutID uint `gorm:"not null;uniqueIndex"` // The workout this data belongs to
Creator string // The tool that created this workout
Name string // The name of the workout
Center MapCenter `gorm:"serializer:json"` // The center of the workout (in coordinates)
Address *geo.Address `gorm:"serializer:json"` // The address of the workout
TotalDistance float64 // The total distance of the workout
TotalDuration time.Duration // The total duration of the workout
MaxSpeed float64 // The maximum speed of the workout
PauseDuration time.Duration // The total pause duration of the workout
MinElevation float64 // The minimum elevation of the workout
MaxElevation float64 // The maximum elevation of the workout
TotalUp float64 // The total distance up of the workout
TotalDown float64 // The total distance down of the workout
Details *MapDataDetails `json:",omitempty"` // The details of the workout
TotalRepetitions int // The number of repetitions of the workout
TotalWeight float64 // The weight of the workout

Points []MapPoint `gorm:"serializer:json" json:"-"` // To be removed
}
Expand Down
5 changes: 5 additions & 0 deletions views/equipment/equipment_show.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ <h3 class="grow justify-start {{ IconFor `totals` }}">
<th>{{ i18n "Total duration" }}</th>
<td>{{ .Duration | HumanDuration }}</td>
</tr>
<tr>
<td class="{{ IconFor `repetitions` }}"></td>
<th>{{ i18n "Total repetitions" }}</th>
<td>{{ .Repetitions }}</td>
</tr>
</tbody>
</table>
{{ end }}
Expand Down
Loading

0 comments on commit 4101e4a

Please sign in to comment.