SplitOutputTable.vue (3437B)
1 <template> 2 <table class="split-output-table"> 3 <thead> 4 <tr> 5 <th> 6 <span>Distance</span> 7 <span class="mobile-abbreviation">Dist.</span> 8 </th> 9 10 <th>Time</th> 11 12 <th>Split</th> 13 14 <th>Pace</th> 15 </tr> 16 </thead> 17 18 <tbody> 19 <tr v-for="(item, index) in results" :key="index"> 20 <td> 21 {{ formatDistance(model[index] as Distance, false) }} 22 </td> 23 24 <td> 25 {{ formatDuration(item.total.time, 3, 2, true) }} 26 </td> 27 28 <td> 29 <time-input v-model="model[index].splitTime" label="Split duration" :showHours="false"/> 30 </td> 31 32 <td> 33 {{ formatPace(item.split, getDefaultPaceUnit(defaultUnitSystem)) }} 34 </td> 35 </tr> 36 37 <tr v-if="results.length === 0" class="empty-message"> 38 <td colspan="5"> 39 There aren't any targets in this set yet. 40 </td> 41 </tr> 42 </tbody> 43 </table> 44 </template> 45 46 <script setup lang="ts"> 47 import { computed } from 'vue'; 48 49 import type { SplitTarget } from '@/core/targets'; 50 import { DistanceUnits, UnitSystems, convertDistance, formatDistance, formatDuration, 51 formatPace, getDefaultPaceUnit } from '@/core/units'; 52 import type { Distance, DistanceTime } from '@/core/units'; 53 54 import TimeInput from '@/components/TimeInput.vue'; 55 import useObjectModel from '@/composables/useObjectModel'; 56 57 interface SplitTargetResult { 58 split: DistanceTime, 59 total: DistanceTime, 60 }; 61 62 interface Props { 63 /** 64 * The unit system to use when showing result paces (defaults to metric) 65 */ 66 defaultUnitSystem?: UnitSystems, 67 68 /** 69 * The split targets 70 */ 71 modelValue: Array<SplitTarget>, 72 }; 73 74 const props = withDefaults(defineProps<Props>(), { defaultUnitSystem: UnitSystems.Metric }); 75 76 // Generate internal ref tied to modelValue prop 77 const emit = defineEmits(['update:modelValue']); 78 const model = useObjectModel<Array<SplitTarget>>(() => props.modelValue, 79 (x) => emit('update:modelValue', x)); 80 81 /** 82 * The target table results 83 */ 84 const results = computed(() => { 85 // Initialize results array 86 const results: Array<SplitTargetResult> = []; 87 88 for (let i = 0; i < model.value.length; i += 1) { 89 // Calculate split and total times 90 const splitTime = model.value[i].splitTime || 0; 91 const totalTime = i === 0 ? splitTime : results[i - 1].total.time + splitTime; 92 93 // Calculate split and total distances 94 const totalDistance = convertDistance( 95 model.value[i].distanceValue, 96 model.value[i].distanceUnit, 97 DistanceUnits.Meters, 98 ); 99 const splitDistance = i === 0 ? totalDistance : 100 totalDistance - results[i - 1].total.distanceValue; 101 102 // Add row to results array 103 results.push({ 104 split: { 105 distanceValue: splitDistance, 106 distanceUnit: DistanceUnits.Meters, 107 time: splitTime, 108 }, 109 total: { 110 distanceValue: totalDistance, 111 distanceUnit: DistanceUnits.Meters, 112 time: totalTime, 113 }, 114 }); 115 } 116 117 // Return results array 118 return results; 119 }); 120 </script> 121 122 <style scoped> 123 /* Show/hide mobile abbreviations */ 124 .split-output-table th:first-child span.mobile-abbreviation { 125 display: none; 126 } 127 @media only screen and (max-width: 500px) { 128 .split-output-table th:first-child span:not(.mobile-abbreviation) { 129 display: none; 130 } 131 .split-output-table th:first-child span.mobile-abbreviation { 132 display: inherit; 133 } 134 } 135 </style>