From 94ca1ea1933e8bad9a76a6045f7d9750f9b6b401 Mon Sep 17 00:00:00 2001 From: Fabio Manganiello <fabio@manganiello.tech> Date: Mon, 31 Mar 2025 22:11:31 +0200 Subject: [PATCH] Made the map timeline more mobile-friendly --- frontend/src/components/Timeline.vue | 96 +++++++++++++-------- frontend/src/components/TimelineOptions.vue | 89 +++++++++++++++++++ 2 files changed, 148 insertions(+), 37 deletions(-) create mode 100644 frontend/src/components/TimelineOptions.vue diff --git a/frontend/src/components/Timeline.vue b/frontend/src/components/Timeline.vue index 1c4dd3d..4ecb7bb 100644 --- a/frontend/src/components/Timeline.vue +++ b/frontend/src/components/Timeline.vue @@ -3,31 +3,24 @@ <h1 v-if="loading">Loading...</h1> <h1 v-else-if="!points.length">No data to display</h1> <div class="body" v-else> - <div class="options"> - <button @click="toggleMetric('altitude')" - :class="{ selected: showMetrics.altitude }" - :title="(showMetrics.altitude ? 'Hide' : 'Show') + ' altitude'"> - <font-awesome-icon icon="mountain" /> + <div class="options-toggle until tablet"> + <button @click="optionsVisible = !optionsVisible" + :title="(optionsVisible ? 'Hide' : 'Show') + ' options'" + :class="{ selected: optionsVisible }"> + <font-awesome-icon icon="bars" /> </button> + </div> - <button @click="toggleMetric('distance')" - :class="{ selected: showMetrics.distance }" - :title="(showMetrics.distance ? 'Hide' : 'Show') + ' distance'"> - <font-awesome-icon icon="ruler" /> - </button> + <div class="options-container from tablet"> + <TimelineOptions :showMetrics="showMetrics" + :hasBatteryInfo="hasBatteryInfo" + @show-metrics="$emit('show-metrics', $event)" /> + </div> - <button @click="toggleMetric('speed')" - :class="{ selected: showMetrics.speed }" - :title="(showMetrics.speed ? 'Hide' : 'Show') + ' speed'"> - <font-awesome-icon icon="tachometer-alt" /> - </button> - - <button @click="toggleMetric('battery')" - :class="{ selected: showMetrics.battery }" - :title="(showMetrics.battery ? 'Hide' : 'Show') + ' battery level'" - v-if="hasBatteryInfo"> - <font-awesome-icon icon="battery-full" /> - </button> + <div class="options-container until tablet" v-if="optionsVisible"> + <TimelineOptions :showMetrics="showMetrics" + :hasBatteryInfo="hasBatteryInfo" + @show-metrics="$emit('show-metrics', $event)" /> </div> <div class="page-button-container"> @@ -48,7 +41,7 @@ </button> </div> - <div class="page-button-container" + <div class="page-button-container reset-pagination" v-if="locationQuery?.minId || locationQuery?.maxId"> <button @click="$emit('reset-page')" title="Reset pagination"> @@ -78,6 +71,7 @@ import Geo from '../mixins/Geo.vue'; import GPSPoint from '../models/GPSPoint'; import LocationQuery from '../models/LocationQuery'; import TimelineMetricsConfiguration from '../models/TimelineMetricsConfiguration'; +import TimelineOptions from './TimelineOptions.vue'; ChartJS.register( CategoryScale, @@ -97,9 +91,11 @@ export default { 'reset-page', 'show-metrics', ], + mixins: [Geo], components: { Line, + TimelineOptions, }, props: { @@ -120,6 +116,12 @@ export default { }, }, + data() { + return { + optionsVisible: false, + } + }, + computed: { distances(): number[] { if (!this.points.length) { @@ -394,22 +396,18 @@ $options-width: 5em; } } -.options { - display: flex; - flex-direction: column; - justify-content: space-around; - align-items: center; - width: $options-width; - height: 100%; - margin-right: 1em; +.options-toggle { + position: absolute; + width: 2em; + height: 2em; + z-index: 1; button { width: 100%; - height: 2.5em; + height: 100%; font-size: 1em; - background-color: var(--color-background); - border: 1px solid var(--vt-c-divider-light-1); - margin-left: 0.5em; + background-color: none; + border: 0; cursor: pointer; &:hover { @@ -417,12 +415,28 @@ $options-width: 5em; } &.selected { - background: var(--vt-c-blue-bg-dark); - color: var(--vt-c-white); + color: var(--color-accent); } } } +.options-container { + width: $options-width; + height: 100%; + padding: 0.5em; + + :deep(button) { + font-size: 0.8em; + } + + &.until.tablet { + position: absolute; + left: 2.5em; + background-color: var(--color-background); + box-shadow: 0.25em 0.25em 0.5em 0.1em var(--color-border); + } +} + .page-button-container { width: 3em; height: 100%; @@ -444,4 +458,12 @@ $options-width: 5em; } } } + +.reset-pagination { + width: 2em; + height: 2em; + position: absolute; + top: 0; + right: 0; +} </style> diff --git a/frontend/src/components/TimelineOptions.vue b/frontend/src/components/TimelineOptions.vue new file mode 100644 index 0000000..a9933cf --- /dev/null +++ b/frontend/src/components/TimelineOptions.vue @@ -0,0 +1,89 @@ +<template> + <div class="options desktop"> + <button @click="toggleMetric('altitude')" + :class="{ selected: showMetrics.altitude }" + :title="(showMetrics.altitude ? 'Hide' : 'Show') + ' altitude'"> + <font-awesome-icon icon="mountain" /> + </button> + + <button @click="toggleMetric('distance')" + :class="{ selected: showMetrics.distance }" + :title="(showMetrics.distance ? 'Hide' : 'Show') + ' distance'"> + <font-awesome-icon icon="ruler" /> + </button> + + <button @click="toggleMetric('speed')" + :class="{ selected: showMetrics.speed }" + :title="(showMetrics.speed ? 'Hide' : 'Show') + ' speed'"> + <font-awesome-icon icon="tachometer-alt" /> + </button> + + <button @click="toggleMetric('battery')" + :class="{ selected: showMetrics.battery }" + :title="(showMetrics.battery ? 'Hide' : 'Show') + ' battery level'" + v-if="hasBatteryInfo"> + <font-awesome-icon icon="battery-full" /> + </button> + </div> +</template> + +<script lang="ts"> +import TimelineMetricsConfiguration from '../models/TimelineMetricsConfiguration'; + +export default { + emits: [ + 'show-metrics', + ], + + props: { + hasBatteryInfo: { + type: Boolean, + default: false, + }, + showMetrics: { + type: TimelineMetricsConfiguration, + default: () => new TimelineMetricsConfiguration(), + }, + }, + + methods: { + toggleMetric(metric: string) { + this.$emit('show-metrics', { + ...this.showMetrics, + [metric]: !(this.showMetrics as any)[metric], + }); + }, + }, +} +</script> + +<style scoped lang="scss"> +.options { + display: flex; + flex-direction: column; + justify-content: space-around; + align-items: center; + width: 100%; + height: 100%; + margin-right: 1em; + + button { + width: 100%; + height: 2.5em; + font-size: 1em; + background-color: var(--color-background); + border: 1px solid var(--vt-c-divider-light-1); + margin-left: 0.5em; + cursor: pointer; + + &:hover { + color: var(--color-hover); + } + + &.selected { + background: var(--vt-c-blue-bg-dark); + color: var(--vt-c-white); + } + } +} +</style>