Merge branch 'dev' into alternate_weather_api
This commit is contained in:
@ -54,7 +54,7 @@
|
||||
.dropdown-list {
|
||||
list-style-type: none;
|
||||
background-color: #ffffff;
|
||||
padding: 0;
|
||||
padding: 0 !important;
|
||||
margin-top: 5px;
|
||||
margin-left: -20px !important;
|
||||
position: absolute;
|
||||
@ -64,16 +64,12 @@
|
||||
width: auto !important;
|
||||
|
||||
li {
|
||||
padding-top: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
li:last-child {
|
||||
padding-bottom: 5px;
|
||||
padding: 3px 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
cursor: default;
|
||||
cursor: pointer;
|
||||
|
||||
&.selected {
|
||||
font-weight: bold;
|
||||
@ -82,5 +78,9 @@
|
||||
&.selected::after {
|
||||
content: ' ✔';
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--dropdown-hover-color);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<svg
|
||||
version="1.1"
|
||||
id="Capa_1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 495.017 495.017"
|
||||
style="enable-background: new 0 0 495.017 495.017"
|
||||
xml:space="preserve"
|
||||
>
|
||||
<desc id="mountaineeringDescription">
|
||||
silhouette of a person doing mountaineering
|
||||
</desc>
|
||||
<g>
|
||||
<path
|
||||
d="M271.32,78.354c21.633,0,39.192-17.54,39.192-39.182C310.513,17.541,292.953,0,271.32,0
|
||||
c-21.633,0-39.175,17.541-39.175,39.173C232.146,60.814,249.688,78.354,271.32,78.354z"
|
||||
/>
|
||||
<path
|
||||
d="M312.763,349.373l-47.021-43.163l2.087-15.884l-70.055-4.301c-4.173,6.412-9.963,14.482-17.193,23.104l-19.17,72.416
|
||||
l-54.928,73.797c-8.185,11.007-5.92,26.567,5.095,34.76c4.449,3.314,9.658,4.916,14.817,4.916c7.583,0,15.058-3.461,19.941-10.011
|
||||
l57.794-77.641c1.893-2.539,3.284-5.417,4.092-8.475l16.625-62.781l45.518,41.786l-6.211,90.579
|
||||
c-0.938,13.691,9.381,25.548,23.083,26.485c0.565,0.041,1.146,0.057,1.73,0.057c12.938,0,23.859-10.035,24.748-23.146
|
||||
l7.036-102.498C321.269,361.843,318.325,354.477,312.763,349.373z"
|
||||
/>
|
||||
<path
|
||||
d="M398.667,183.428h-70.005l-39.482-56.053l0.13-0.956c1.405-10.708-1.506-21.537-8.091-30.101
|
||||
c-6.581-8.564-16.288-14.16-26.994-15.567c-11.858-1.545-23.1,2.271-31.54,9.429V43.548c0-8.022-6.503-14.532-14.542-14.532
|
||||
h-40.277c-8.038,0-14.542,6.51-14.542,14.532v22.637h-12.033c-8.586,0-16.628,4.255-21.463,11.363
|
||||
c-4.836,7.108-5.838,16.149-2.684,24.149l36.18,91.856v22.718c0,11.152,4.58,21.125,11.876,28.427
|
||||
c5.046-6.082,12.743-9.713,20.526-9.195l88.571,5.443l6.938-52.94l19.767,28.047c3.881,5.507,10.19,8.783,16.921,8.783h80.744
|
||||
c11.437,0,20.701-9.268,20.701-20.702C419.367,192.696,410.104,183.428,398.667,183.428z"
|
||||
/>
|
||||
<path
|
||||
d="M191.093,273.166l82.25,5.055c7.117,0.55,12.748-4.789,13.166-11.64c0.422-6.849-4.786-12.737-11.643-13.157l-89.903-5.523
|
||||
c-5.015-0.348-9.573,2.352-11.805,6.737c-2.039,3.97-50.771,96.822-143.537,83.768c-6.662-0.906-13.068,3.777-14.023,10.569
|
||||
c-0.954,6.794,3.768,13.078,10.563,14.032c7.198,1.011,14.169,1.488,20.895,1.488C128.224,364.487,177.119,295.915,191.093,273.166
|
||||
z"
|
||||
/>
|
||||
<path
|
||||
d="M476.659,251.313c-14.223-9.793-36.401-18.891-66.555-18.488c-35.468,0.473-68.598,27.748-69.198,28.412
|
||||
c-6.13,6.835-5.562,17.348,1.278,23.478c6.824,6.139,17.339,5.564,23.469-1.268l0.114-0.13c0,0,19.495-20.418,36.814-24.925
|
||||
l-5.822,215.844c0,0.307,0,0.678,0,0.994c0.289,10.198,8.765,18.236,18.955,17.963c10.206-0.276,18.245-8.759,17.985-18.957
|
||||
l-5.951-221.215c27.271,0.444,43.739,8.938,43.739,8.938c2.65,1.035,5.741,0.022,7.242-2.501
|
||||
C480.414,256.635,479.359,253.172,476.659,251.313z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Mountaineering',
|
||||
}
|
||||
</script>
|
@ -12,6 +12,7 @@
|
||||
<MountainBikingElectric
|
||||
v-if="sportLabel === 'Mountain Biking (Electric)'"
|
||||
/>
|
||||
<Mountaineering v-if="sportLabel === 'Mountaineering'" />
|
||||
<Rowing v-if="sportLabel === 'Rowing'" />
|
||||
<Running v-if="sportLabel === 'Running'" />
|
||||
<SkiingAlpine v-if="sportLabel === 'Skiing (Alpine)'" />
|
||||
@ -31,6 +32,7 @@
|
||||
import Hiking from '@/components/Common/Images/SportImage/Hiking.vue'
|
||||
import MountainBiking from '@/components/Common/Images/SportImage/MountainBiking.vue'
|
||||
import MountainBikingElectric from '@/components/Common/Images/SportImage/MountainBikingElectric.vue'
|
||||
import Mountaineering from '@/components/Common/Images/SportImage/Mountaineering.vue'
|
||||
import Rowing from '@/components/Common/Images/SportImage/Rowing.vue'
|
||||
import Running from '@/components/Common/Images/SportImage/Running.vue'
|
||||
import SkiingAlpine from '@/components/Common/Images/SportImage/SkiingAlpine.vue'
|
||||
|
@ -5,8 +5,8 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ChartData, ChartOptions, LayoutItem } from 'chart.js'
|
||||
import { ComputedRef, PropType, computed, defineComponent } from 'vue'
|
||||
import { ChartOptions, LayoutItem } from 'chart.js'
|
||||
import { PropType, computed, defineComponent } from 'vue'
|
||||
import { BarChart, useBarChart } from 'vue-chart-3'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@ -60,7 +60,7 @@
|
||||
? 'm'
|
||||
: 'km'
|
||||
}
|
||||
const chartData: ComputedRef<ChartData<'bar'>> = computed(() => ({
|
||||
const chartData = computed(() => ({
|
||||
labels: props.labels,
|
||||
// workaround to avoid dataset modification
|
||||
datasets: JSON.parse(JSON.stringify(props.datasets)),
|
||||
|
@ -197,14 +197,9 @@
|
||||
.nav-item {
|
||||
padding: 0 10px;
|
||||
|
||||
&.dropdown-wrapper {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
::v-deep(.dropdown-list) {
|
||||
margin-left: -10px;
|
||||
padding-left: 10px;
|
||||
width: 75px;
|
||||
margin-left: -95px !important;
|
||||
width: 115px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@ -294,6 +289,11 @@
|
||||
|
||||
.nav-item {
|
||||
padding: 7px 25px;
|
||||
|
||||
::v-deep(.dropdown-list) {
|
||||
margin-left: initial !important;
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-profile-img {
|
||||
|
@ -242,5 +242,10 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#language,
|
||||
#date_format {
|
||||
padding: $default-padding * 0.5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -114,7 +114,7 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data altitude" v-if="workout && workout.with_gpx">
|
||||
<div class="data altitude" v-if="hasElevation(workout)">
|
||||
<i class="fa fa-location-arrow" aria-hidden="true" />
|
||||
<div class="data-values">
|
||||
+<Distance
|
||||
@ -167,6 +167,10 @@
|
||||
const locale: ComputedRef<Locale> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.LOCALE]
|
||||
)
|
||||
|
||||
function hasElevation(workout: IWorkout): boolean {
|
||||
return workout && workout.ascent !== null && workout.descent !== null
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -108,7 +108,7 @@
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
[displayDistance.value ? 'xDistance' : 'xDuration']: {
|
||||
x: {
|
||||
grid: {
|
||||
drawOnChartArea: false,
|
||||
},
|
||||
|
@ -3,7 +3,13 @@
|
||||
<Card>
|
||||
<template #title>{{ $t('workouts.NOTES') }}</template>
|
||||
<template #content>
|
||||
{{ notes && notes !== '' ? notes : $t('workouts.NO_NOTES') }}
|
||||
<span
|
||||
v-html="
|
||||
notes && notes !== ''
|
||||
? linkifyAndClean(notes)
|
||||
: $t('workouts.NO_NOTES')
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</Card>
|
||||
</div>
|
||||
@ -12,6 +18,8 @@
|
||||
<script setup lang="ts">
|
||||
import { toRefs, withDefaults } from 'vue'
|
||||
|
||||
import { linkifyAndClean } from '@/utils/inputs'
|
||||
|
||||
interface Props {
|
||||
notes?: string | null
|
||||
}
|
||||
|
@ -37,7 +37,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label> {{ $t('workouts.SPORT', 1) }}: </label>
|
||||
<label> {{ $t('workouts.SPORT', 1) }}*: </label>
|
||||
<select
|
||||
id="sport"
|
||||
required
|
||||
@ -57,7 +57,7 @@
|
||||
<div class="form-item" v-if="isCreation && withGpx">
|
||||
<label for="gpxFile">
|
||||
{{ $t('workouts.GPX_FILE') }}
|
||||
{{ $t('workouts.ZIP_ARCHIVE_DESCRIPTION') }}:
|
||||
{{ $t('workouts.ZIP_ARCHIVE_DESCRIPTION') }}*:
|
||||
</label>
|
||||
<input
|
||||
id="gpxFile"
|
||||
@ -105,7 +105,7 @@
|
||||
<div v-if="!withGpx">
|
||||
<div class="workout-date-duration">
|
||||
<div class="form-item">
|
||||
<label>{{ $t('workouts.WORKOUT_DATE') }}:</label>
|
||||
<label>{{ $t('workouts.WORKOUT_DATE') }}*:</label>
|
||||
<div class="workout-date-time">
|
||||
<input
|
||||
id="workout-date"
|
||||
@ -129,12 +129,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label>{{ $t('workouts.DURATION') }}:</label>
|
||||
<label>{{ $t('workouts.DURATION') }}*:</label>
|
||||
<div>
|
||||
<input
|
||||
id="workout-duration-hour"
|
||||
name="workout-duration-hour"
|
||||
class="workout-duration"
|
||||
:class="{ errored: isDurationInvalid() }"
|
||||
type="text"
|
||||
placeholder="HH"
|
||||
minlength="1"
|
||||
@ -150,6 +151,7 @@
|
||||
id="workout-duration-minutes"
|
||||
name="workout-duration-minutes"
|
||||
class="workout-duration"
|
||||
:class="{ errored: isDurationInvalid() }"
|
||||
type="text"
|
||||
pattern="^([0-5][0-9])$"
|
||||
minlength="2"
|
||||
@ -165,6 +167,7 @@
|
||||
id="workout-duration-seconds"
|
||||
name="workout-duration-seconds"
|
||||
class="workout-duration"
|
||||
:class="{ errored: isDurationInvalid() }"
|
||||
type="text"
|
||||
pattern="^([0-5][0-9])$"
|
||||
minlength="2"
|
||||
@ -178,22 +181,59 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label>
|
||||
{{ $t('workouts.DISTANCE') }} ({{
|
||||
authUser.imperial_units ? 'mi' : 'km'
|
||||
}}):
|
||||
</label>
|
||||
<input
|
||||
name="workout-distance"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.001"
|
||||
required
|
||||
@invalid="invalidateForm"
|
||||
:disabled="loading"
|
||||
v-model="workoutForm.workoutDistance"
|
||||
/>
|
||||
<div class="workout-data">
|
||||
<div class="form-item">
|
||||
<label>
|
||||
{{ $t('workouts.DISTANCE') }} ({{
|
||||
authUser.imperial_units ? 'mi' : 'km'
|
||||
}})*:
|
||||
</label>
|
||||
<input
|
||||
:class="{ errored: isDistanceInvalid() }"
|
||||
name="workout-distance"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.001"
|
||||
required
|
||||
@invalid="invalidateForm"
|
||||
:disabled="loading"
|
||||
v-model="workoutForm.workoutDistance"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label>
|
||||
{{ $t('workouts.ASCENT') }} ({{
|
||||
authUser.imperial_units ? 'ft' : 'm'
|
||||
}}):
|
||||
</label>
|
||||
<input
|
||||
:class="{ errored: isElevationInvalid() }"
|
||||
name="workout-ascent"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
@invalid="invalidateForm"
|
||||
:disabled="loading"
|
||||
v-model="workoutForm.workoutAscent"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label>
|
||||
{{ $t('workouts.DESCENT') }} ({{
|
||||
authUser.imperial_units ? 'ft' : 'm'
|
||||
}}):
|
||||
</label>
|
||||
<input
|
||||
:class="{ errored: isElevationInvalid() }"
|
||||
name="workout-descent"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
@invalid="invalidateForm"
|
||||
:disabled="loading"
|
||||
v-model="workoutForm.workoutDescent"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
@ -228,6 +268,7 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ComputedRef,
|
||||
Ref,
|
||||
computed,
|
||||
reactive,
|
||||
ref,
|
||||
@ -300,12 +341,15 @@
|
||||
workoutDurationMinutes: '',
|
||||
workoutDurationSeconds: '',
|
||||
workoutDistance: '',
|
||||
workoutAscent: '',
|
||||
workoutDescent: '',
|
||||
})
|
||||
const withGpx = ref(
|
||||
props.workout.id ? props.workout.with_gpx : props.isCreation
|
||||
)
|
||||
let gpxFile: File | null = null
|
||||
const formErrors = ref(false)
|
||||
const payloadErrorMessages: Ref<string[]> = ref([])
|
||||
|
||||
onMounted(() => {
|
||||
if (props.workout.id) {
|
||||
@ -337,26 +381,78 @@
|
||||
const duration = workout.duration.split(':')
|
||||
workoutForm.workoutDistance = `${
|
||||
authUser.value.imperial_units
|
||||
? convertDistance(workout.distance, 'km', 'mi', 2)
|
||||
: parseFloat(workout.distance.toFixed(2))
|
||||
? convertDistance(workout.distance, 'km', 'mi', 3)
|
||||
: parseFloat(workout.distance.toFixed(3))
|
||||
}`
|
||||
workoutForm.workoutDate = workoutDateTime.workout_date
|
||||
workoutForm.workoutTime = workoutDateTime.workout_time
|
||||
workoutForm.workoutDurationHour = duration[0]
|
||||
workoutForm.workoutDurationMinutes = duration[1]
|
||||
workoutForm.workoutDurationSeconds = duration[2]
|
||||
workoutForm.workoutAscent =
|
||||
workout.ascent === null
|
||||
? ''
|
||||
: `${
|
||||
authUser.value.imperial_units
|
||||
? convertDistance(workout.ascent, 'm', 'ft', 2)
|
||||
: parseFloat(workout.ascent.toFixed(2))
|
||||
}`
|
||||
workoutForm.workoutDescent =
|
||||
workout.descent === null
|
||||
? ''
|
||||
: `${
|
||||
authUser.value.imperial_units
|
||||
? convertDistance(workout.descent, 'm', 'ft', 2)
|
||||
: parseFloat(workout.descent.toFixed(2))
|
||||
}`
|
||||
}
|
||||
}
|
||||
function isDistanceInvalid() {
|
||||
return payloadErrorMessages.value.includes('workouts.INVALID_DISTANCE')
|
||||
}
|
||||
function isDurationInvalid() {
|
||||
return payloadErrorMessages.value.includes('workouts.INVALID_DURATION')
|
||||
}
|
||||
function isElevationInvalid() {
|
||||
return payloadErrorMessages.value.includes(
|
||||
'workouts.INVALID_ASCENT_OR_DESCENT'
|
||||
)
|
||||
}
|
||||
function formatPayload(payload: IWorkoutForm) {
|
||||
payloadErrorMessages.value = []
|
||||
payload.title = workoutForm.title
|
||||
payload.distance = authUser.value.imperial_units
|
||||
? convertDistance(+workoutForm.workoutDistance, 'mi', 'km', 3)
|
||||
: +workoutForm.workoutDistance
|
||||
payload.duration =
|
||||
+workoutForm.workoutDurationHour * 3600 +
|
||||
+workoutForm.workoutDurationMinutes * 60 +
|
||||
+workoutForm.workoutDurationSeconds
|
||||
if (payload.duration <= 0) {
|
||||
payloadErrorMessages.value.push('workouts.INVALID_DURATION')
|
||||
}
|
||||
payload.distance = authUser.value.imperial_units
|
||||
? convertDistance(+workoutForm.workoutDistance, 'mi', 'km', 3)
|
||||
: +workoutForm.workoutDistance
|
||||
if (payload.distance <= 0) {
|
||||
payloadErrorMessages.value.push('workouts.INVALID_DISTANCE')
|
||||
}
|
||||
payload.workout_date = `${workoutForm.workoutDate} ${workoutForm.workoutTime}`
|
||||
payload.ascent =
|
||||
workoutForm.workoutAscent === ''
|
||||
? null
|
||||
: authUser.value.imperial_units
|
||||
? convertDistance(+workoutForm.workoutAscent, 'ft', 'm', 3)
|
||||
: +workoutForm.workoutAscent
|
||||
payload.descent =
|
||||
workoutForm.workoutDescent === ''
|
||||
? null
|
||||
: authUser.value.imperial_units
|
||||
? convertDistance(+workoutForm.workoutDescent, 'ft', 'm', 3)
|
||||
: +workoutForm.workoutDescent
|
||||
if (
|
||||
(payload.ascent !== null && payload.descent === null) ||
|
||||
(payload.ascent === null && payload.descent !== null)
|
||||
) {
|
||||
payloadErrorMessages.value.push('workouts.INVALID_ASCENT_OR_DESCENT')
|
||||
}
|
||||
}
|
||||
function updateWorkout() {
|
||||
const payload: IWorkoutForm = {
|
||||
@ -369,10 +465,17 @@
|
||||
} else {
|
||||
formatPayload(payload)
|
||||
}
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.EDIT_WORKOUT, {
|
||||
workoutId: props.workout.id,
|
||||
data: payload,
|
||||
})
|
||||
if (payloadErrorMessages.value.length > 0) {
|
||||
store.commit(
|
||||
ROOT_STORE.MUTATIONS.SET_ERROR_MESSAGES,
|
||||
payloadErrorMessages.value
|
||||
)
|
||||
} else {
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.EDIT_WORKOUT, {
|
||||
workoutId: props.workout.id,
|
||||
data: payload,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (withGpx.value) {
|
||||
if (!gpxFile) {
|
||||
@ -384,7 +487,17 @@
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.ADD_WORKOUT, payload)
|
||||
} else {
|
||||
formatPayload(payload)
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.ADD_WORKOUT_WITHOUT_GPX, payload)
|
||||
if (payloadErrorMessages.value.length > 0) {
|
||||
store.commit(
|
||||
ROOT_STORE.MUTATIONS.SET_ERROR_MESSAGES,
|
||||
payloadErrorMessages.value
|
||||
)
|
||||
} else {
|
||||
store.dispatch(
|
||||
WORKOUTS_STORE.ACTIONS.ADD_WORKOUT_WITHOUT_GPX,
|
||||
payload
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -507,6 +620,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.workout-data {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
.form-item {
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: $medium-limit) {
|
||||
flex-direction: column;
|
||||
.form-item {
|
||||
width: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -521,5 +650,9 @@
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.errored {
|
||||
outline: 2px solid var(--input-error-color);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -134,7 +134,7 @@
|
||||
{{ $t('workouts.ASCENT') }}
|
||||
</span>
|
||||
<Distance
|
||||
v-if="workout.with_gpx"
|
||||
v-if="workout.ascent !== null"
|
||||
:distance="workout.ascent"
|
||||
unitFrom="m"
|
||||
:useImperialUnits="user.imperial_units"
|
||||
@ -145,7 +145,7 @@
|
||||
{{ $t('workouts.DESCENT') }}
|
||||
</span>
|
||||
<Distance
|
||||
v-if="workout.with_gpx"
|
||||
v-if="workout.descent !== null"
|
||||
:distance="workout.descent"
|
||||
unitFrom="m"
|
||||
:useImperialUnits="user.imperial_units"
|
||||
|
Reference in New Issue
Block a user