Client - use <script setup> in components
This commit is contained in:
@ -17,7 +17,7 @@
|
||||
</div>
|
||||
<router-link
|
||||
class="workout-title"
|
||||
v-if="workout"
|
||||
v-if="workout.id"
|
||||
:to="{
|
||||
name: 'Workout',
|
||||
params: { workoutId: workout.id },
|
||||
@ -27,7 +27,7 @@
|
||||
</router-link>
|
||||
<div
|
||||
class="workout-date"
|
||||
v-if="workout && user"
|
||||
v-if="workout.workout_date && user"
|
||||
:title="
|
||||
format(
|
||||
getDateWithTZ(workout.workout_date, user.timezone),
|
||||
@ -47,7 +47,7 @@
|
||||
class="workout-map"
|
||||
:class="{ 'no-cursor': !workout }"
|
||||
@click="
|
||||
workout
|
||||
workout.id
|
||||
? $router.push({
|
||||
name: 'Workout',
|
||||
params: { workoutId: workout.id },
|
||||
@ -66,11 +66,16 @@
|
||||
class="workout-data"
|
||||
:class="{ 'without-gpx': workout && !workout.with_gpx }"
|
||||
@click="
|
||||
$router.push({ name: 'Workout', params: { workoutId: workout.id } })
|
||||
workout.id
|
||||
? $router.push({
|
||||
name: 'Workout',
|
||||
params: { workoutId: workout.id },
|
||||
})
|
||||
: null
|
||||
"
|
||||
>
|
||||
<div class="img">
|
||||
<SportImage v-if="sport" :sport-label="sport.label" />
|
||||
<SportImage v-if="sport.label" :sport-label="sport.label" />
|
||||
</div>
|
||||
<div class="data">
|
||||
<i class="fa fa-clock-o" aria-hidden="true" />
|
||||
@ -103,9 +108,9 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { Locale, format, formatDistance } from 'date-fns'
|
||||
import { PropType, defineComponent, ComputedRef, computed } from 'vue'
|
||||
import { ComputedRef, computed, toRefs, withDefaults } from 'vue'
|
||||
|
||||
import StaticMap from '@/components/Common/StaticMap.vue'
|
||||
import UserPicture from '@/components/User/UserPicture.vue'
|
||||
@ -116,39 +121,22 @@
|
||||
import { useStore } from '@/use/useStore'
|
||||
import { getDateWithTZ } from '@/utils/dates'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WorkoutCard',
|
||||
components: {
|
||||
StaticMap,
|
||||
UserPicture,
|
||||
},
|
||||
props: {
|
||||
workout: {
|
||||
type: Object as PropType<IWorkout>,
|
||||
required: false,
|
||||
},
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
sport: {
|
||||
type: Object as PropType<ISport>,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const store = useStore()
|
||||
const locale: ComputedRef<Locale> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.LOCALE]
|
||||
)
|
||||
return {
|
||||
format,
|
||||
formatDistance,
|
||||
getDateWithTZ,
|
||||
locale,
|
||||
}
|
||||
},
|
||||
interface Props {
|
||||
user: IUserProfile
|
||||
workout?: IWorkout
|
||||
sport?: ISport
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
workout: () => ({} as IWorkout),
|
||||
sport: () => ({} as ISport),
|
||||
})
|
||||
|
||||
const store = useStore()
|
||||
|
||||
const { user, workout, sport } = toRefs(props)
|
||||
const locale: ComputedRef<Locale> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.LOCALE]
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -36,9 +36,9 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { ChartData, ChartOptions } from 'chart.js'
|
||||
import { ComputedRef, PropType, computed, defineComponent, ref } from 'vue'
|
||||
import { ComputedRef, computed, ref } from 'vue'
|
||||
import { LineChart, useLineChart } from 'vue-chart-3'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@ -50,160 +50,142 @@
|
||||
} from '@/types/workouts'
|
||||
import { getDatasets } from '@/utils/workouts'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WorkoutChart',
|
||||
components: {
|
||||
LineChart,
|
||||
},
|
||||
props: {
|
||||
authUser: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
workoutData: {
|
||||
type: Object as PropType<IWorkoutData>,
|
||||
required: true,
|
||||
interface Props {
|
||||
authUser: IUserProfile
|
||||
workoutData: IWorkoutData
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const emit = defineEmits(['getCoordinates'])
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
let displayDistance = ref(true)
|
||||
const datasets: ComputedRef<IWorkoutChartData> = computed(() =>
|
||||
getDatasets(props.workoutData.chartData, t)
|
||||
)
|
||||
let chartData: ComputedRef<ChartData<'line'>> = computed(() => ({
|
||||
labels: displayDistance.value
|
||||
? datasets.value.distance_labels
|
||||
: datasets.value.duration_labels,
|
||||
datasets: JSON.parse(
|
||||
JSON.stringify([
|
||||
datasets.value.datasets.speed,
|
||||
datasets.value.datasets.elevation,
|
||||
])
|
||||
),
|
||||
}))
|
||||
const coordinates: ComputedRef<TCoordinates[]> = computed(
|
||||
() => datasets.value.coordinates
|
||||
)
|
||||
const options = computed<ChartOptions<'line'>>(() => ({
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
animation: false,
|
||||
layout: {
|
||||
padding: {
|
||||
top: 22,
|
||||
},
|
||||
},
|
||||
emits: ['getCoordinates'],
|
||||
setup(props, { emit }) {
|
||||
const { t } = useI18n()
|
||||
|
||||
let displayDistance = ref(true)
|
||||
const datasets: ComputedRef<IWorkoutChartData> = computed(() =>
|
||||
getDatasets(props.workoutData.chartData, t)
|
||||
)
|
||||
let chartData: ComputedRef<ChartData<'line'>> = computed(() => ({
|
||||
labels: displayDistance.value
|
||||
? datasets.value.distance_labels
|
||||
: datasets.value.duration_labels,
|
||||
datasets: JSON.parse(
|
||||
JSON.stringify([
|
||||
datasets.value.datasets.speed,
|
||||
datasets.value.datasets.elevation,
|
||||
])
|
||||
),
|
||||
}))
|
||||
const coordinates: ComputedRef<TCoordinates[]> = computed(
|
||||
() => datasets.value.coordinates
|
||||
)
|
||||
const options = computed<ChartOptions<'line'>>(() => ({
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
animation: false,
|
||||
layout: {
|
||||
padding: {
|
||||
top: 22,
|
||||
scales: {
|
||||
[displayDistance.value ? 'xDistance' : 'xDuration']: {
|
||||
grid: {
|
||||
drawOnChartArea: false,
|
||||
},
|
||||
ticks: {
|
||||
count: 10,
|
||||
callback: function (value) {
|
||||
return displayDistance.value
|
||||
? Number(value).toFixed(2)
|
||||
: formatDuration(value)
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
[displayDistance.value ? 'xDistance' : 'xDuration']: {
|
||||
grid: {
|
||||
drawOnChartArea: false,
|
||||
},
|
||||
ticks: {
|
||||
count: 10,
|
||||
callback: function (value) {
|
||||
return displayDistance.value
|
||||
? Number(value).toFixed(2)
|
||||
: formatDuration(value)
|
||||
},
|
||||
},
|
||||
type: 'linear',
|
||||
bounds: 'data',
|
||||
title: {
|
||||
display: true,
|
||||
text: displayDistance.value
|
||||
? t('workouts.DISTANCE') + ' (km)'
|
||||
: t('workouts.DURATION'),
|
||||
},
|
||||
},
|
||||
ySpeed: {
|
||||
grid: {
|
||||
drawOnChartArea: false,
|
||||
},
|
||||
position: 'left',
|
||||
title: {
|
||||
display: true,
|
||||
text: t('workouts.SPEED') + ' (km/h)',
|
||||
},
|
||||
},
|
||||
yElevation: {
|
||||
beginAtZero: true,
|
||||
grid: {
|
||||
drawOnChartArea: false,
|
||||
},
|
||||
position: 'right',
|
||||
title: {
|
||||
display: true,
|
||||
text: t('workouts.ELEVATION') + ' (m)',
|
||||
},
|
||||
},
|
||||
type: 'linear',
|
||||
bounds: 'data',
|
||||
title: {
|
||||
display: true,
|
||||
text: displayDistance.value
|
||||
? t('workouts.DISTANCE') + ' (km)'
|
||||
: t('workouts.DURATION'),
|
||||
},
|
||||
elements: {
|
||||
point: {
|
||||
pointStyle: 'circle',
|
||||
pointRadius: 0,
|
||||
},
|
||||
},
|
||||
ySpeed: {
|
||||
grid: {
|
||||
drawOnChartArea: false,
|
||||
},
|
||||
plugins: {
|
||||
datalabels: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'index',
|
||||
},
|
||||
callbacks: {
|
||||
label: function (context) {
|
||||
const label = ` ${context.dataset.label}: ${context.formattedValue}`
|
||||
return context.dataset.yAxisID === 'yElevation'
|
||||
? label + ' m'
|
||||
: label + ' km/h'
|
||||
},
|
||||
title: function (tooltipItems) {
|
||||
if (tooltipItems.length > 0) {
|
||||
emitCoordinates(coordinates.value[tooltipItems[0].dataIndex])
|
||||
}
|
||||
return tooltipItems.length === 0
|
||||
? ''
|
||||
: displayDistance.value
|
||||
? `${t('workouts.DISTANCE')}: ${tooltipItems[0].label} km`
|
||||
: `${t('workouts.DURATION')}: ${formatDuration(
|
||||
tooltipItems[0].label.replace(',', '')
|
||||
)}`
|
||||
},
|
||||
},
|
||||
},
|
||||
position: 'left',
|
||||
title: {
|
||||
display: true,
|
||||
text: t('workouts.SPEED') + ' (km/h)',
|
||||
},
|
||||
}))
|
||||
|
||||
function updateDisplayDistance() {
|
||||
displayDistance.value = !displayDistance.value
|
||||
}
|
||||
function formatDuration(duration: string | number): string {
|
||||
return new Date(+duration * 1000).toISOString().substr(11, 8)
|
||||
}
|
||||
function emitCoordinates(coordinates: TCoordinates) {
|
||||
emit('getCoordinates', coordinates)
|
||||
}
|
||||
function emitEmptyCoordinates() {
|
||||
emitCoordinates({ latitude: null, longitude: null })
|
||||
}
|
||||
|
||||
const { lineChartProps } = useLineChart({
|
||||
chartData,
|
||||
options,
|
||||
})
|
||||
return {
|
||||
displayDistance,
|
||||
lineChartProps,
|
||||
emitEmptyCoordinates,
|
||||
updateDisplayDistance,
|
||||
}
|
||||
},
|
||||
yElevation: {
|
||||
beginAtZero: true,
|
||||
grid: {
|
||||
drawOnChartArea: false,
|
||||
},
|
||||
position: 'right',
|
||||
title: {
|
||||
display: true,
|
||||
text: t('workouts.ELEVATION') + ' (m)',
|
||||
},
|
||||
},
|
||||
},
|
||||
elements: {
|
||||
point: {
|
||||
pointStyle: 'circle',
|
||||
pointRadius: 0,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
datalabels: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'index',
|
||||
},
|
||||
callbacks: {
|
||||
label: function (context) {
|
||||
const label = ` ${context.dataset.label}: ${context.formattedValue}`
|
||||
return context.dataset.yAxisID === 'yElevation'
|
||||
? label + ' m'
|
||||
: label + ' km/h'
|
||||
},
|
||||
title: function (tooltipItems) {
|
||||
if (tooltipItems.length > 0) {
|
||||
emitCoordinates(coordinates.value[tooltipItems[0].dataIndex])
|
||||
}
|
||||
return tooltipItems.length === 0
|
||||
? ''
|
||||
: displayDistance.value
|
||||
? `${t('workouts.DISTANCE')}: ${tooltipItems[0].label} km`
|
||||
: `${t('workouts.DURATION')}: ${formatDuration(
|
||||
tooltipItems[0].label.replace(',', '')
|
||||
)}`
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
const { lineChartProps } = useLineChart({
|
||||
chartData,
|
||||
options,
|
||||
})
|
||||
|
||||
function updateDisplayDistance() {
|
||||
displayDistance.value = !displayDistance.value
|
||||
}
|
||||
function formatDuration(duration: string | number): string {
|
||||
return new Date(+duration * 1000).toISOString().substr(11, 8)
|
||||
}
|
||||
function emitCoordinates(coordinates: TCoordinates) {
|
||||
emit('getCoordinates', coordinates)
|
||||
}
|
||||
function emitEmptyCoordinates() {
|
||||
emitCoordinates({ latitude: null, longitude: null })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -80,29 +80,21 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
import { ISport } from '@/types/sports'
|
||||
import { IWorkoutObject } from '@/types/workouts'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WorkoutCardTitle',
|
||||
props: {
|
||||
sport: {
|
||||
type: Object as PropType<ISport>,
|
||||
required: true,
|
||||
},
|
||||
workoutObject: {
|
||||
type: Object as PropType<IWorkoutObject>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ['displayModal'],
|
||||
setup(props, { emit }) {
|
||||
return { emit }
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
sport: ISport
|
||||
workoutObject: IWorkoutObject
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const emit = defineEmits(['displayModal'])
|
||||
|
||||
const { sport, workoutObject } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<div class="workout-data">
|
||||
<i class="fa fa-clock-o" aria-hidden="true" />
|
||||
{{ $t('workouts.DURATION') }}: <span>{{ workoutObject.moving }}</span>
|
||||
<WorkoutRecord :workoutObject="workoutObject" record_type="LD" />
|
||||
<WorkoutRecord :workoutObject="workoutObject" recordType="LD" />
|
||||
<div v-if="withPause">
|
||||
({{ $t('workouts.PAUSES') }}: <span>{{ workoutObject.pauses }}</span> -
|
||||
{{ $t('workouts.TOTAL_DURATION') }}:
|
||||
@ -14,16 +14,16 @@
|
||||
<i class="fa fa-road" aria-hidden="true" />
|
||||
{{ $t('workouts.DISTANCE') }}:
|
||||
<span>{{ workoutObject.distance }} km</span>
|
||||
<WorkoutRecord :workoutObject="workoutObject" record_type="FD" />
|
||||
<WorkoutRecord :workoutObject="workoutObject" recordType="FD" />
|
||||
</div>
|
||||
<div class="workout-data">
|
||||
<i class="fa fa-tachometer" aria-hidden="true" />
|
||||
{{ $t('workouts.AVERAGE_SPEED') }}:
|
||||
<span>{{ workoutObject.aveSpeed }} km/h</span
|
||||
><WorkoutRecord :workoutObject="workoutObject" record_type="AS" /><br />
|
||||
><WorkoutRecord :workoutObject="workoutObject" recordType="AS" /><br />
|
||||
{{ $t('workouts.MAX_SPEED') }}:
|
||||
<span>{{ workoutObject.maxSpeed }} km/h</span>
|
||||
<WorkoutRecord :workoutObject="workoutObject" record_type="MS" />
|
||||
<WorkoutRecord :workoutObject="workoutObject" recordType="MS" />
|
||||
</div>
|
||||
<div
|
||||
class="workout-data"
|
||||
@ -52,35 +52,24 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, computed, defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { computed, toRefs } from 'vue'
|
||||
|
||||
import WorkoutRecord from '@/components/Workout/WorkoutDetail/WorkoutRecord.vue'
|
||||
import WorkoutWeather from '@/components/Workout/WorkoutDetail/WorkoutWeather.vue'
|
||||
import { IWorkoutObject } from '@/types/workouts'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WorkoutData',
|
||||
components: {
|
||||
WorkoutRecord,
|
||||
WorkoutWeather,
|
||||
},
|
||||
props: {
|
||||
workoutObject: {
|
||||
type: Object as PropType<IWorkoutObject>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
return {
|
||||
withPause: computed(
|
||||
() =>
|
||||
props.workoutObject.pauses !== '0:00:00' &&
|
||||
props.workoutObject.pauses !== null
|
||||
),
|
||||
}
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
workoutObject: IWorkoutObject
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { workoutObject } = toRefs(props)
|
||||
const withPause = computed(
|
||||
() =>
|
||||
props.workoutObject.pauses !== '0:00:00' &&
|
||||
props.workoutObject.pauses !== null
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -28,10 +28,10 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { gpx } from '@tmcw/togeojson'
|
||||
import { LGeoJson, LMap, LMarker, LTileLayer } from '@vue-leaflet/vue-leaflet'
|
||||
import { ComputedRef, PropType, computed, defineComponent, ref } from 'vue'
|
||||
import { ComputedRef, computed, ref, toRefs, withDefaults } from 'vue'
|
||||
|
||||
import { ROOT_STORE } from '@/store/constants'
|
||||
import { TAppConfig } from '@/types/application'
|
||||
@ -40,90 +40,69 @@
|
||||
import { useStore } from '@/use/useStore'
|
||||
import { getApiUrl } from '@/utils'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WorkoutMap',
|
||||
components: {
|
||||
LGeoJson,
|
||||
LMap,
|
||||
LMarker,
|
||||
LTileLayer,
|
||||
},
|
||||
props: {
|
||||
workoutData: {
|
||||
type: Object as PropType<IWorkoutData>,
|
||||
},
|
||||
markerCoordinates: {
|
||||
type: Object as PropType<TCoordinates>,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore()
|
||||
interface Props {
|
||||
workoutData: IWorkoutData
|
||||
markerCoordinates?: TCoordinates
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
markerCoordinates: () => ({} as TCoordinates),
|
||||
})
|
||||
|
||||
function getGeoJson(gpxContent: string): GeoJSONData {
|
||||
if (!gpxContent || gpxContent !== '') {
|
||||
try {
|
||||
const jsonData = gpx(
|
||||
new DOMParser().parseFromString(gpxContent, 'text/xml')
|
||||
)
|
||||
return { jsonData }
|
||||
} catch (e) {
|
||||
console.error('Invalid gpx content')
|
||||
return {}
|
||||
}
|
||||
}
|
||||
const store = useStore()
|
||||
|
||||
const { workoutData, markerCoordinates } = toRefs(props)
|
||||
const workoutMap = ref<null | {
|
||||
leafletObject: { fitBounds: (bounds: number[][]) => null }
|
||||
}>(null)
|
||||
const bounds = computed(() =>
|
||||
props.workoutData
|
||||
? [
|
||||
[
|
||||
props.workoutData.workout.bounds[0],
|
||||
props.workoutData.workout.bounds[1],
|
||||
],
|
||||
[
|
||||
props.workoutData.workout.bounds[2],
|
||||
props.workoutData.workout.bounds[3],
|
||||
],
|
||||
]
|
||||
: []
|
||||
)
|
||||
const appConfig: ComputedRef<TAppConfig> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
|
||||
)
|
||||
const center = computed(() => getCenter(bounds))
|
||||
const geoJson = computed(() =>
|
||||
props.workoutData && props.workoutData.gpx
|
||||
? getGeoJson(props.workoutData.gpx)
|
||||
: {}
|
||||
)
|
||||
|
||||
function getGeoJson(gpxContent: string): GeoJSONData {
|
||||
if (!gpxContent || gpxContent !== '') {
|
||||
try {
|
||||
const jsonData = gpx(
|
||||
new DOMParser().parseFromString(gpxContent, 'text/xml')
|
||||
)
|
||||
return { jsonData }
|
||||
} catch (e) {
|
||||
console.error('Invalid gpx content')
|
||||
return {}
|
||||
}
|
||||
function getCenter(bounds: ComputedRef<number[][]>): number[] {
|
||||
return [
|
||||
(bounds.value[0][0] + bounds.value[1][0]) / 2,
|
||||
(bounds.value[0][1] + bounds.value[1][1]) / 2,
|
||||
]
|
||||
}
|
||||
function fitBounds(bounds: number[][]) {
|
||||
if (workoutMap.value?.leafletObject) {
|
||||
workoutMap.value?.leafletObject.fitBounds(bounds)
|
||||
}
|
||||
}
|
||||
|
||||
const workoutMap = ref<null | {
|
||||
leafletObject: { fitBounds: (bounds: number[][]) => null }
|
||||
}>(null)
|
||||
const bounds = computed(() =>
|
||||
props.workoutData
|
||||
? [
|
||||
[
|
||||
props.workoutData.workout.bounds[0],
|
||||
props.workoutData.workout.bounds[1],
|
||||
],
|
||||
[
|
||||
props.workoutData.workout.bounds[2],
|
||||
props.workoutData.workout.bounds[3],
|
||||
],
|
||||
]
|
||||
: []
|
||||
)
|
||||
const appConfig: ComputedRef<TAppConfig> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
|
||||
)
|
||||
const center = computed(() => getCenter(bounds))
|
||||
const geoJson = computed(() =>
|
||||
props.workoutData && props.workoutData.gpx
|
||||
? getGeoJson(props.workoutData.gpx)
|
||||
: {}
|
||||
)
|
||||
|
||||
return {
|
||||
appConfig,
|
||||
bounds,
|
||||
center,
|
||||
geoJson,
|
||||
workoutMap,
|
||||
fitBounds,
|
||||
getApiUrl,
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
return {}
|
||||
}
|
||||
function getCenter(bounds: ComputedRef<number[][]>): number[] {
|
||||
return [
|
||||
(bounds.value[0][0] + bounds.value[1][0]) / 2,
|
||||
(bounds.value[0][1] + bounds.value[1][1]) / 2,
|
||||
]
|
||||
}
|
||||
function fitBounds(bounds: number[][]) {
|
||||
if (workoutMap.value?.leafletObject) {
|
||||
workoutMap.value?.leafletObject.fitBounds(bounds)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -3,7 +3,7 @@
|
||||
class="workout-record"
|
||||
v-if="
|
||||
workoutObject.records &&
|
||||
workoutObject.records.find((record) => record.record_type === record_type)
|
||||
workoutObject.records.find((record) => record.record_type === recordType)
|
||||
"
|
||||
>
|
||||
<sup>
|
||||
@ -12,24 +12,18 @@
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
import { IWorkoutObject } from '@/types/workouts'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WorkoutRecord',
|
||||
props: {
|
||||
record_type: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
workoutObject: {
|
||||
type: Object as PropType<IWorkoutObject>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
recordType: string
|
||||
workoutObject: IWorkoutObject
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { recordType, workoutObject } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -97,20 +97,17 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
import { IWorkoutObject } from '@/types/workouts'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WorkoutWeather',
|
||||
props: {
|
||||
workoutObject: {
|
||||
type: Object as PropType<IWorkoutObject>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
workoutObject: IWorkoutObject
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { workoutObject } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -26,15 +26,15 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ComputedRef,
|
||||
PropType,
|
||||
Ref,
|
||||
computed,
|
||||
defineComponent,
|
||||
ref,
|
||||
toRefs,
|
||||
watch,
|
||||
withDefaults,
|
||||
} from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
@ -54,147 +54,122 @@
|
||||
import { useStore } from '@/use/useStore'
|
||||
import { formatWorkoutDate, getDateWithTZ } from '@/utils/dates'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WorkoutDetail',
|
||||
components: {
|
||||
WorkoutCardTitle,
|
||||
WorkoutData,
|
||||
WorkoutMap,
|
||||
},
|
||||
props: {
|
||||
authUser: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
displaySegment: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
markerCoordinates: {
|
||||
type: Object as PropType<TCoordinates>,
|
||||
required: false,
|
||||
},
|
||||
sports: {
|
||||
type: Object as PropType<ISport[]>,
|
||||
},
|
||||
workoutData: {
|
||||
type: Object as PropType<IWorkoutData>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const route = useRoute()
|
||||
const store = useStore()
|
||||
|
||||
function getWorkoutObjectUrl(
|
||||
workout: IWorkout,
|
||||
displaySegment: boolean,
|
||||
segmentId: number | null
|
||||
): Record<string, string | null> {
|
||||
const previousUrl =
|
||||
displaySegment && segmentId && segmentId !== 1
|
||||
? `/workouts/${workout.id}/segment/${segmentId - 1}`
|
||||
: !displaySegment && workout.previous_workout
|
||||
? `/workouts/${workout.previous_workout}`
|
||||
: null
|
||||
const nextUrl =
|
||||
displaySegment && segmentId && segmentId < workout.segments.length
|
||||
? `/workouts/${workout.id}/segment/${segmentId + 1}`
|
||||
: !displaySegment && workout.next_workout
|
||||
? `/workouts/${workout.next_workout}`
|
||||
: null
|
||||
return {
|
||||
previousUrl,
|
||||
nextUrl,
|
||||
}
|
||||
}
|
||||
function getWorkoutObject(
|
||||
workout: IWorkout,
|
||||
segment: IWorkoutSegment | null
|
||||
): IWorkoutObject {
|
||||
const urls = getWorkoutObjectUrl(
|
||||
workout,
|
||||
props.displaySegment,
|
||||
segmentId.value ? +segmentId.value : null
|
||||
)
|
||||
const workoutDate = formatWorkoutDate(
|
||||
getDateWithTZ(
|
||||
props.workoutData.workout.workout_date,
|
||||
props.authUser.timezone
|
||||
)
|
||||
)
|
||||
return {
|
||||
ascent: segment ? segment.ascent : workout.ascent,
|
||||
aveSpeed: segment ? segment.ave_speed : workout.ave_speed,
|
||||
distance: segment ? segment.distance : workout.distance,
|
||||
descent: segment ? segment.descent : workout.descent,
|
||||
duration: segment ? segment.duration : workout.duration,
|
||||
maxAlt: segment ? segment.max_alt : workout.max_alt,
|
||||
maxSpeed: segment ? segment.max_speed : workout.max_speed,
|
||||
minAlt: segment ? segment.min_alt : workout.min_alt,
|
||||
moving: segment ? segment.moving : workout.moving,
|
||||
nextUrl: urls.nextUrl,
|
||||
pauses: segment ? segment.pauses : workout.pauses,
|
||||
previousUrl: urls.previousUrl,
|
||||
records: segment ? [] : workout.records,
|
||||
segmentId: segment ? segment.segment_id : null,
|
||||
title: workout.title,
|
||||
type: props.displaySegment ? 'SEGMENT' : 'WORKOUT',
|
||||
workoutDate: workoutDate.workout_date,
|
||||
weatherEnd: segment ? null : workout.weather_end,
|
||||
workoutId: workout.id,
|
||||
weatherStart: segment ? null : workout.weather_start,
|
||||
workoutTime: workoutDate.workout_time,
|
||||
}
|
||||
}
|
||||
function updateDisplayModal(value: boolean) {
|
||||
displayModal.value = value
|
||||
}
|
||||
function deleteWorkout(workoutId: string) {
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.DELETE_WORKOUT, {
|
||||
workoutId: workoutId,
|
||||
})
|
||||
}
|
||||
|
||||
const workout: ComputedRef<IWorkout> = computed(
|
||||
() => props.workoutData.workout
|
||||
)
|
||||
let segmentId: Ref<number | null> = ref(
|
||||
route.params.workoutId ? +route.params.segmentId : null
|
||||
)
|
||||
const segment: ComputedRef<IWorkoutSegment | null> = computed(() =>
|
||||
workout.value.segments.length > 0 && segmentId.value
|
||||
? workout.value.segments[+segmentId.value - 1]
|
||||
: null
|
||||
)
|
||||
let displayModal: Ref<boolean> = ref(false)
|
||||
|
||||
watch(
|
||||
() => route.params.segmentId,
|
||||
async (newSegmentId) => {
|
||||
if (newSegmentId) {
|
||||
segmentId.value = +newSegmentId
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
sport: computed(() =>
|
||||
props.sports
|
||||
? props.sports.find(
|
||||
(sport) => sport.id === props.workoutData.workout.sport_id
|
||||
)
|
||||
: {}
|
||||
),
|
||||
workoutObject: computed(() =>
|
||||
getWorkoutObject(workout.value, segment.value)
|
||||
),
|
||||
displayModal,
|
||||
deleteWorkout,
|
||||
updateDisplayModal,
|
||||
}
|
||||
},
|
||||
interface Props {
|
||||
authUser: IUserProfile
|
||||
displaySegment: boolean
|
||||
sports: ISport[]
|
||||
workoutData: IWorkoutData
|
||||
markerCoordinates?: TCoordinates
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
markerCoordinates: () => ({} as TCoordinates),
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const store = useStore()
|
||||
|
||||
const { markerCoordinates, workoutData } = toRefs(props)
|
||||
const workout: ComputedRef<IWorkout> = computed(
|
||||
() => props.workoutData.workout
|
||||
)
|
||||
let segmentId: Ref<number | null> = ref(
|
||||
route.params.workoutId ? +route.params.segmentId : null
|
||||
)
|
||||
const segment: ComputedRef<IWorkoutSegment | null> = computed(() =>
|
||||
workout.value.segments.length > 0 && segmentId.value
|
||||
? workout.value.segments[+segmentId.value - 1]
|
||||
: null
|
||||
)
|
||||
let displayModal: Ref<boolean> = ref(false)
|
||||
const sport = computed(() =>
|
||||
props.sports
|
||||
? props.sports.find(
|
||||
(sport) => sport.id === props.workoutData.workout.sport_id
|
||||
)
|
||||
: {}
|
||||
)
|
||||
const workoutObject = computed(() =>
|
||||
getWorkoutObject(workout.value, segment.value)
|
||||
)
|
||||
|
||||
function getWorkoutObjectUrl(
|
||||
workout: IWorkout,
|
||||
displaySegment: boolean,
|
||||
segmentId: number | null
|
||||
): Record<string, string | null> {
|
||||
const previousUrl =
|
||||
displaySegment && segmentId && segmentId !== 1
|
||||
? `/workouts/${workout.id}/segment/${segmentId - 1}`
|
||||
: !displaySegment && workout.previous_workout
|
||||
? `/workouts/${workout.previous_workout}`
|
||||
: null
|
||||
const nextUrl =
|
||||
displaySegment && segmentId && segmentId < workout.segments.length
|
||||
? `/workouts/${workout.id}/segment/${segmentId + 1}`
|
||||
: !displaySegment && workout.next_workout
|
||||
? `/workouts/${workout.next_workout}`
|
||||
: null
|
||||
return {
|
||||
previousUrl,
|
||||
nextUrl,
|
||||
}
|
||||
}
|
||||
function getWorkoutObject(
|
||||
workout: IWorkout,
|
||||
segment: IWorkoutSegment | null
|
||||
): IWorkoutObject {
|
||||
const urls = getWorkoutObjectUrl(
|
||||
workout,
|
||||
props.displaySegment,
|
||||
segmentId.value ? +segmentId.value : null
|
||||
)
|
||||
const workoutDate = formatWorkoutDate(
|
||||
getDateWithTZ(
|
||||
props.workoutData.workout.workout_date,
|
||||
props.authUser.timezone
|
||||
)
|
||||
)
|
||||
return {
|
||||
ascent: segment ? segment.ascent : workout.ascent,
|
||||
aveSpeed: segment ? segment.ave_speed : workout.ave_speed,
|
||||
distance: segment ? segment.distance : workout.distance,
|
||||
descent: segment ? segment.descent : workout.descent,
|
||||
duration: segment ? segment.duration : workout.duration,
|
||||
maxAlt: segment ? segment.max_alt : workout.max_alt,
|
||||
maxSpeed: segment ? segment.max_speed : workout.max_speed,
|
||||
minAlt: segment ? segment.min_alt : workout.min_alt,
|
||||
moving: segment ? segment.moving : workout.moving,
|
||||
nextUrl: urls.nextUrl,
|
||||
pauses: segment ? segment.pauses : workout.pauses,
|
||||
previousUrl: urls.previousUrl,
|
||||
records: segment ? [] : workout.records,
|
||||
segmentId: segment ? segment.segment_id : null,
|
||||
title: workout.title,
|
||||
type: props.displaySegment ? 'SEGMENT' : 'WORKOUT',
|
||||
workoutDate: workoutDate.workout_date,
|
||||
weatherEnd: segment ? null : workout.weather_end,
|
||||
workoutId: workout.id,
|
||||
weatherStart: segment ? null : workout.weather_start,
|
||||
workoutTime: workoutDate.workout_time,
|
||||
}
|
||||
}
|
||||
function updateDisplayModal(value: boolean) {
|
||||
displayModal.value = value
|
||||
}
|
||||
function deleteWorkout(workoutId: string) {
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.DELETE_WORKOUT, {
|
||||
workoutId: workoutId,
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.params.segmentId,
|
||||
async (newSegmentId) => {
|
||||
if (newSegmentId) {
|
||||
segmentId.value = +newSegmentId
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -31,9 +31,9 @@
|
||||
:disabled="loading"
|
||||
@click="updateWithGpx"
|
||||
/>
|
||||
<label for="withoutGpx">{{
|
||||
$t('workouts.WITHOUT_GPX')
|
||||
}}</label>
|
||||
<label for="withoutGpx">
|
||||
{{ $t('workouts.WITHOUT_GPX') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
@ -42,7 +42,7 @@
|
||||
id="sport"
|
||||
required
|
||||
:disabled="loading"
|
||||
v-model="workoutDataObject.sport_id"
|
||||
v-model="workoutForm.sport_id"
|
||||
>
|
||||
<option
|
||||
v-for="sport in translatedSports.filter((s) => s.is_active)"
|
||||
@ -95,7 +95,7 @@
|
||||
type="text"
|
||||
:required="!isCreation"
|
||||
:disabled="loading"
|
||||
v-model="workoutDataObject.title"
|
||||
v-model="workoutForm.title"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="!withGpx">
|
||||
@ -109,7 +109,7 @@
|
||||
type="date"
|
||||
required
|
||||
:disabled="loading"
|
||||
v-model="workoutDataObject.workoutDate"
|
||||
v-model="workoutForm.workoutDate"
|
||||
/>
|
||||
<input
|
||||
id="workout-time"
|
||||
@ -118,7 +118,7 @@
|
||||
type="time"
|
||||
required
|
||||
:disabled="loading"
|
||||
v-model="workoutDataObject.workoutTime"
|
||||
v-model="workoutForm.workoutTime"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -134,7 +134,7 @@
|
||||
pattern="^([0-9]*[0-9])$"
|
||||
required
|
||||
:disabled="loading"
|
||||
v-model="workoutDataObject.workoutDurationHour"
|
||||
v-model="workoutForm.workoutDurationHour"
|
||||
/>
|
||||
:
|
||||
<input
|
||||
@ -146,7 +146,7 @@
|
||||
placeholder="MM"
|
||||
required
|
||||
:disabled="loading"
|
||||
v-model="workoutDataObject.workoutDurationMinutes"
|
||||
v-model="workoutForm.workoutDurationMinutes"
|
||||
/>
|
||||
:
|
||||
<input
|
||||
@ -158,7 +158,7 @@
|
||||
placeholder="SS"
|
||||
required
|
||||
:disabled="loading"
|
||||
v-model="workoutDataObject.workoutDurationSeconds"
|
||||
v-model="workoutForm.workoutDurationSeconds"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -172,7 +172,7 @@
|
||||
step="0.1"
|
||||
required
|
||||
:disabled="loading"
|
||||
v-model="workoutDataObject.workoutDistance"
|
||||
v-model="workoutForm.workoutDistance"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -180,7 +180,7 @@
|
||||
<label> {{ $t('workouts.NOTES') }}: </label>
|
||||
<CustomTextArea
|
||||
name="notes"
|
||||
:input="workoutDataObject.notes"
|
||||
:input="workoutForm.notes"
|
||||
:disabled="loading"
|
||||
@updateValue="updateNotes"
|
||||
/>
|
||||
@ -205,17 +205,17 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ComputedRef,
|
||||
PropType,
|
||||
defineComponent,
|
||||
computed,
|
||||
reactive,
|
||||
ref,
|
||||
toRefs,
|
||||
watch,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
withDefaults,
|
||||
} from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRouter } from 'vue-router'
|
||||
@ -230,189 +230,154 @@
|
||||
import { getReadableFileSize } from '@/utils/files'
|
||||
import { translateSports } from '@/utils/sports'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WorkoutEdition',
|
||||
props: {
|
||||
authUser: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
isCreation: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
sports: {
|
||||
type: Object as PropType<ISport[]>,
|
||||
required: true,
|
||||
},
|
||||
workout: {
|
||||
type: Object as PropType<IWorkout>,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { t } = useI18n()
|
||||
const store = useStore()
|
||||
const router = useRouter()
|
||||
|
||||
onMounted(() => {
|
||||
if (props.workout && props.workout.id) {
|
||||
formatWorkoutForm(props.workout)
|
||||
}
|
||||
})
|
||||
|
||||
const translatedSports: ComputedRef<ISport[]> = computed(() =>
|
||||
translateSports(props.sports, t)
|
||||
)
|
||||
const appConfig: ComputedRef<TAppConfig> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
|
||||
)
|
||||
const fileSizeLimit = appConfig.value.max_single_file_size
|
||||
? getReadableFileSize(appConfig.value.max_single_file_size)
|
||||
: ''
|
||||
const gpx_limit_import = appConfig.value.gpx_limit_import
|
||||
const zipSizeLimit = appConfig.value.max_zip_file_size
|
||||
? getReadableFileSize(appConfig.value.max_zip_file_size)
|
||||
: ''
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
const workoutForm = reactive({
|
||||
sport_id: '',
|
||||
title: '',
|
||||
notes: '',
|
||||
workoutDate: '',
|
||||
workoutTime: '',
|
||||
workoutDurationHour: '',
|
||||
workoutDurationMinutes: '',
|
||||
workoutDurationSeconds: '',
|
||||
workoutDistance: '',
|
||||
})
|
||||
let withGpx = ref(
|
||||
props.workout ? props.workout.with_gpx : props.isCreation
|
||||
)
|
||||
let gpxFile: File | null = null
|
||||
|
||||
function updateNotes(value: string) {
|
||||
workoutForm.notes = value
|
||||
}
|
||||
function updateWithGpx() {
|
||||
withGpx.value = !withGpx.value
|
||||
}
|
||||
function updateFile(event: Event & { target: HTMLInputElement }) {
|
||||
if (event.target.files) {
|
||||
gpxFile = event.target.files[0]
|
||||
}
|
||||
}
|
||||
function formatWorkoutForm(workout: IWorkout) {
|
||||
workoutForm.sport_id = `${workout.sport_id}`
|
||||
workoutForm.title = workout.title
|
||||
workoutForm.notes = workout.notes
|
||||
if (!workout.with_gpx) {
|
||||
const workoutDateTime = formatWorkoutDate(
|
||||
getDateWithTZ(workout.workout_date, props.authUser.timezone),
|
||||
'yyyy-MM-dd'
|
||||
)
|
||||
const duration = workout.duration.split(':')
|
||||
workoutForm.workoutDistance = `${workout.distance}`
|
||||
workoutForm.workoutDate = workoutDateTime.workout_date
|
||||
workoutForm.workoutTime = workoutDateTime.workout_time
|
||||
workoutForm.workoutDurationHour = duration[0]
|
||||
workoutForm.workoutDurationMinutes = duration[1]
|
||||
workoutForm.workoutDurationSeconds = duration[2]
|
||||
}
|
||||
}
|
||||
function formatPayload(payload: IWorkoutForm) {
|
||||
payload.title = workoutForm.title
|
||||
payload.distance = +workoutForm.workoutDistance
|
||||
payload.duration =
|
||||
+workoutForm.workoutDurationHour * 3600 +
|
||||
+workoutForm.workoutDurationMinutes * 60 +
|
||||
+workoutForm.workoutDurationSeconds
|
||||
payload.workout_date = `${workoutForm.workoutDate} ${workoutForm.workoutTime}`
|
||||
}
|
||||
function updateWorkout() {
|
||||
const payload: IWorkoutForm = {
|
||||
sport_id: +workoutForm.sport_id,
|
||||
notes: workoutForm.notes,
|
||||
}
|
||||
if (props.workout) {
|
||||
if (props.workout.with_gpx) {
|
||||
payload.title = workoutForm.title
|
||||
} else {
|
||||
formatPayload(payload)
|
||||
}
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.EDIT_WORKOUT, {
|
||||
workoutId: props.workout.id,
|
||||
data: payload,
|
||||
})
|
||||
} else {
|
||||
if (withGpx.value) {
|
||||
if (!gpxFile) {
|
||||
const errorMessage = 'workouts.NO_FILE_PROVIDED'
|
||||
store.commit(
|
||||
ROOT_STORE.MUTATIONS.SET_ERROR_MESSAGES,
|
||||
errorMessage
|
||||
)
|
||||
return
|
||||
}
|
||||
payload.file = gpxFile
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.ADD_WORKOUT, payload)
|
||||
} else {
|
||||
formatPayload(payload)
|
||||
store.dispatch(
|
||||
WORKOUTS_STORE.ACTIONS.ADD_WORKOUT_WITHOUT_GPX,
|
||||
payload
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
function onCancel() {
|
||||
if (props.workout) {
|
||||
router.push({
|
||||
name: 'Workout',
|
||||
params: { workoutId: props.workout.id },
|
||||
})
|
||||
} else {
|
||||
router.go(-1)
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.workout,
|
||||
async (
|
||||
newWorkout: IWorkout | undefined,
|
||||
previousWorkout: IWorkout | undefined
|
||||
) => {
|
||||
if (newWorkout !== previousWorkout && newWorkout && newWorkout.id) {
|
||||
formatWorkoutForm(newWorkout)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
onUnmounted(() => store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES))
|
||||
|
||||
return {
|
||||
appConfig,
|
||||
errorMessages,
|
||||
fileSizeLimit,
|
||||
gpx_limit_import,
|
||||
translatedSports,
|
||||
withGpx,
|
||||
zipSizeLimit,
|
||||
workoutDataObject: workoutForm,
|
||||
onCancel,
|
||||
updateFile,
|
||||
updateNotes,
|
||||
updateWithGpx,
|
||||
updateWorkout,
|
||||
}
|
||||
},
|
||||
interface Props {
|
||||
authUser: IUserProfile
|
||||
sports: ISport[]
|
||||
isCreation?: boolean
|
||||
loading?: boolean
|
||||
workout?: IWorkout
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
isCreation: false,
|
||||
loading: false,
|
||||
workout: () => ({} as IWorkout),
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const store = useStore()
|
||||
const router = useRouter()
|
||||
|
||||
const { workout, isCreation, loading } = toRefs(props)
|
||||
const translatedSports: ComputedRef<ISport[]> = computed(() =>
|
||||
translateSports(props.sports, t)
|
||||
)
|
||||
const appConfig: ComputedRef<TAppConfig> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
|
||||
)
|
||||
const fileSizeLimit = appConfig.value.max_single_file_size
|
||||
? getReadableFileSize(appConfig.value.max_single_file_size)
|
||||
: ''
|
||||
const gpx_limit_import = appConfig.value.gpx_limit_import
|
||||
const zipSizeLimit = appConfig.value.max_zip_file_size
|
||||
? getReadableFileSize(appConfig.value.max_zip_file_size)
|
||||
: ''
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
const workoutForm = reactive({
|
||||
sport_id: '',
|
||||
title: '',
|
||||
notes: '',
|
||||
workoutDate: '',
|
||||
workoutTime: '',
|
||||
workoutDurationHour: '',
|
||||
workoutDurationMinutes: '',
|
||||
workoutDurationSeconds: '',
|
||||
workoutDistance: '',
|
||||
})
|
||||
let withGpx = ref(
|
||||
props.workout.id ? props.workout.with_gpx : props.isCreation
|
||||
)
|
||||
let gpxFile: File | null = null
|
||||
|
||||
onMounted(() => {
|
||||
if (props.workout.id) {
|
||||
formatWorkoutForm(props.workout)
|
||||
}
|
||||
})
|
||||
|
||||
function updateNotes(value: string) {
|
||||
workoutForm.notes = value
|
||||
}
|
||||
function updateWithGpx() {
|
||||
withGpx.value = !withGpx.value
|
||||
}
|
||||
function updateFile(event: Event & { target: HTMLInputElement }) {
|
||||
if (event.target.files) {
|
||||
gpxFile = event.target.files[0]
|
||||
}
|
||||
}
|
||||
function formatWorkoutForm(workout: IWorkout) {
|
||||
workoutForm.sport_id = `${workout.sport_id}`
|
||||
workoutForm.title = workout.title
|
||||
workoutForm.notes = workout.notes
|
||||
if (!workout.with_gpx) {
|
||||
const workoutDateTime = formatWorkoutDate(
|
||||
getDateWithTZ(workout.workout_date, props.authUser.timezone),
|
||||
'yyyy-MM-dd'
|
||||
)
|
||||
const duration = workout.duration.split(':')
|
||||
workoutForm.workoutDistance = `${workout.distance}`
|
||||
workoutForm.workoutDate = workoutDateTime.workout_date
|
||||
workoutForm.workoutTime = workoutDateTime.workout_time
|
||||
workoutForm.workoutDurationHour = duration[0]
|
||||
workoutForm.workoutDurationMinutes = duration[1]
|
||||
workoutForm.workoutDurationSeconds = duration[2]
|
||||
}
|
||||
}
|
||||
function formatPayload(payload: IWorkoutForm) {
|
||||
payload.title = workoutForm.title
|
||||
payload.distance = +workoutForm.workoutDistance
|
||||
payload.duration =
|
||||
+workoutForm.workoutDurationHour * 3600 +
|
||||
+workoutForm.workoutDurationMinutes * 60 +
|
||||
+workoutForm.workoutDurationSeconds
|
||||
payload.workout_date = `${workoutForm.workoutDate} ${workoutForm.workoutTime}`
|
||||
}
|
||||
function updateWorkout() {
|
||||
const payload: IWorkoutForm = {
|
||||
sport_id: +workoutForm.sport_id,
|
||||
notes: workoutForm.notes,
|
||||
}
|
||||
if (props.workout.id) {
|
||||
if (props.workout.with_gpx) {
|
||||
payload.title = workoutForm.title
|
||||
} else {
|
||||
formatPayload(payload)
|
||||
}
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.EDIT_WORKOUT, {
|
||||
workoutId: props.workout.id,
|
||||
data: payload,
|
||||
})
|
||||
} else {
|
||||
if (withGpx.value) {
|
||||
if (!gpxFile) {
|
||||
const errorMessage = 'workouts.NO_FILE_PROVIDED'
|
||||
store.commit(ROOT_STORE.MUTATIONS.SET_ERROR_MESSAGES, errorMessage)
|
||||
return
|
||||
}
|
||||
payload.file = gpxFile
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.ADD_WORKOUT, payload)
|
||||
} else {
|
||||
formatPayload(payload)
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.ADD_WORKOUT_WITHOUT_GPX, payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
function onCancel() {
|
||||
if (props.workout.id) {
|
||||
router.push({
|
||||
name: 'Workout',
|
||||
params: { workoutId: props.workout.id },
|
||||
})
|
||||
} else {
|
||||
router.go(-1)
|
||||
}
|
||||
}
|
||||
|
||||
onUnmounted(() => store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES))
|
||||
|
||||
watch(
|
||||
() => props.workout,
|
||||
async (
|
||||
newWorkout: IWorkout | undefined,
|
||||
previousWorkout: IWorkout | undefined
|
||||
) => {
|
||||
if (newWorkout !== previousWorkout && newWorkout && newWorkout.id) {
|
||||
formatWorkoutForm(newWorkout)
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -9,18 +9,17 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs, withDefaults } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WorkoutNotes',
|
||||
props: {
|
||||
notes: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
interface Props {
|
||||
notes?: string | null
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
notes: () => null,
|
||||
})
|
||||
|
||||
const { notes } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -24,20 +24,17 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
import { IWorkoutSegment } from '@/types/workouts'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WorkoutSegments',
|
||||
props: {
|
||||
segments: {
|
||||
type: Object as PropType<IWorkoutSegment[]>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
segments: IWorkoutSegment[]
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { segments } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
Reference in New Issue
Block a user