running-tools

A collection of tools for runners and their coaches
git clone https://git.ashermorgan.net/running-tools/
Log | Files | Refs | README

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>