Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add other types of workouts without location #174

Merged
merged 1 commit into from
Jul 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading