mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-09-02 00:32:33 +00:00
start localization
This commit is contained in:
25
website/src/lib/components/App.svelte
Normal file
25
website/src/lib/components/App.svelte
Normal file
@@ -0,0 +1,25 @@
|
||||
<script lang="ts">
|
||||
import Data from '$lib/components/Data.svelte';
|
||||
import ElevationProfile from '$lib/components/ElevationProfile.svelte';
|
||||
import FileList from '$lib/components/FileList.svelte';
|
||||
import GPXData from '$lib/components/GPXData.svelte';
|
||||
import Map from '$lib/components/Map.svelte';
|
||||
import Menu from '$lib/components/Menu.svelte';
|
||||
import Toolbar from '$lib/components/toolbar/Toolbar.svelte';
|
||||
import LayerControl from '$lib/components/layer-control/LayerControl.svelte';
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col w-screen h-screen">
|
||||
<div class="grow relative">
|
||||
<Menu />
|
||||
<Toolbar />
|
||||
<Map class="h-full" />
|
||||
<LayerControl />
|
||||
<Data />
|
||||
<FileList />
|
||||
</div>
|
||||
<div class="h-60 flex flex-row gap-2 overflow-hidden border">
|
||||
<GPXData />
|
||||
<ElevationProfile />
|
||||
</div>
|
||||
</div>
|
@@ -6,7 +6,7 @@
|
||||
import Chart from 'chart.js/auto';
|
||||
import mapboxgl from 'mapbox-gl';
|
||||
|
||||
import { map, fileCollection, fileOrder, selectedFiles } from '$lib/stores';
|
||||
import { map, fileCollection, fileOrder, selectedFiles, settings } from '$lib/stores';
|
||||
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import {
|
||||
@@ -21,6 +21,28 @@
|
||||
import { GPXFiles } from 'gpx';
|
||||
import { surfaceColors } from '$lib/assets/surfaces';
|
||||
|
||||
import { _ } from 'svelte-i18n';
|
||||
import {
|
||||
getCadenceUnits,
|
||||
getCadenceWithUnits,
|
||||
getConvertedDistance,
|
||||
getConvertedElevation,
|
||||
getConvertedTemperature,
|
||||
getConvertedVelocity,
|
||||
getDistanceUnits,
|
||||
getDistanceWithUnits,
|
||||
getElevationUnits,
|
||||
getElevationWithUnits,
|
||||
getHeartRateUnits,
|
||||
getHeartRateWithUnits,
|
||||
getPowerUnits,
|
||||
getPowerWithUnits,
|
||||
getTemperatureUnits,
|
||||
getTemperatureWithUnits,
|
||||
getVelocityUnits,
|
||||
getVelocityWithUnits
|
||||
} from '$lib/units';
|
||||
|
||||
let canvas: HTMLCanvasElement;
|
||||
let chart: Chart;
|
||||
|
||||
@@ -41,7 +63,7 @@
|
||||
type: 'linear',
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Distance (km)',
|
||||
text: `${$_('quantities.distance')} (${getDistanceUnits()})`,
|
||||
padding: 0,
|
||||
align: 'end'
|
||||
}
|
||||
@@ -50,7 +72,7 @@
|
||||
type: 'linear',
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Elevation (m)',
|
||||
text: `${$_('quantities.elevation')} (${getElevationUnits()})`,
|
||||
padding: 0
|
||||
}
|
||||
}
|
||||
@@ -82,41 +104,34 @@
|
||||
label: function (context: Chart.TooltipContext) {
|
||||
let point = context.raw;
|
||||
if (context.datasetIndex === 0) {
|
||||
let elevation = point.y.toFixed(0);
|
||||
if ($map && marker) {
|
||||
marker.addTo($map);
|
||||
marker.setLngLat(point.coordinates);
|
||||
}
|
||||
return `Elevation: ${elevation} m`;
|
||||
return `${$_('quantities.elevation')}: ${getElevationWithUnits(point.y, false)}`;
|
||||
} else if (context.datasetIndex === 1) {
|
||||
let speed = point.y.toFixed(2);
|
||||
return `Speed: ${speed} km/h`;
|
||||
return `${$settings.velocityUnits === 'speed' ? $_('quantities.speed') : $_('quantities.pace')}: ${getVelocityWithUnits(point.y, false)}`;
|
||||
} else if (context.datasetIndex === 2) {
|
||||
let hr = point.y;
|
||||
return `Heart Rate: ${hr} bpm`;
|
||||
return `${$_('quantities.heartrate')}: ${getHeartRateWithUnits(point.y)}`;
|
||||
} else if (context.datasetIndex === 3) {
|
||||
let cad = point.y;
|
||||
return `Cadence: ${cad} rpm`;
|
||||
return `${$_('quantities.cadence')}: ${getCadenceWithUnits(point.y)}`;
|
||||
} else if (context.datasetIndex === 4) {
|
||||
let atemp = point.y.toFixed(1);
|
||||
return `Temperature: ${atemp} °C`;
|
||||
return `${$_('quantities.temperature')}: ${getTemperatureWithUnits(point.y, false)}`;
|
||||
} else if (context.datasetIndex === 5) {
|
||||
let power = point.y;
|
||||
return `Power: ${power} W`;
|
||||
return `${$_('quantities.power')}: ${getPowerWithUnits(point.y)}`;
|
||||
}
|
||||
},
|
||||
afterBody: function (contexts: Chart.TooltipContext[]) {
|
||||
let context = contexts.filter((context) => context.datasetIndex === 0);
|
||||
if (context.length === 0) return;
|
||||
let point = context[0].raw;
|
||||
let distance = point.x.toFixed(2);
|
||||
let slope = point.slope.toFixed(1);
|
||||
let surface = point.surface ? point.surface : 'unknown';
|
||||
|
||||
return [
|
||||
` Distance: ${distance} km`,
|
||||
` Slope: ${slope} %`,
|
||||
` Surface: ${surface}`
|
||||
` ${$_('quantities.distance')}: ${getDistanceWithUnits(point.x, false)}`,
|
||||
` ${$_('quantities.slope')}: ${slope} %`,
|
||||
` ${$_('quantities.surface')}: ${surface}`
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -134,28 +149,28 @@
|
||||
} = {
|
||||
speed: {
|
||||
id: 'speed',
|
||||
label: 'Speed',
|
||||
units: 'km/h'
|
||||
label: $_('quantities.speed'),
|
||||
units: getVelocityUnits()
|
||||
},
|
||||
hr: {
|
||||
id: 'hr',
|
||||
label: 'Heart Rate',
|
||||
units: 'bpm'
|
||||
label: $_('quantities.heartrate'),
|
||||
units: getHeartRateUnits()
|
||||
},
|
||||
cad: {
|
||||
id: 'cad',
|
||||
label: 'Cadence',
|
||||
units: 'rpm'
|
||||
label: $_('quantities.cadence'),
|
||||
units: getCadenceUnits()
|
||||
},
|
||||
atemp: {
|
||||
id: 'atemp',
|
||||
label: 'Temperature',
|
||||
units: '°C'
|
||||
label: $_('quantities.temperature'),
|
||||
units: getTemperatureUnits()
|
||||
},
|
||||
power: {
|
||||
id: 'power',
|
||||
label: 'Power',
|
||||
units: 'W'
|
||||
label: $_('quantities.power'),
|
||||
units: getPowerUnits()
|
||||
}
|
||||
};
|
||||
|
||||
@@ -211,11 +226,11 @@
|
||||
|
||||
let trackPointsAndStatistics = gpxFiles.getTrackPointsAndStatistics();
|
||||
chart.data.datasets[0] = {
|
||||
label: 'Elevation',
|
||||
label: $_('quantities.elevation'),
|
||||
data: trackPointsAndStatistics.points.map((point, index) => {
|
||||
return {
|
||||
x: trackPointsAndStatistics.statistics.distance[index],
|
||||
y: point.ele ? point.ele : 0,
|
||||
x: getConvertedDistance(trackPointsAndStatistics.statistics.distance[index]),
|
||||
y: point.ele ? getConvertedElevation(point.ele) : 0,
|
||||
slope: trackPointsAndStatistics.statistics.slope[index],
|
||||
surface: point.getSurface(),
|
||||
coordinates: point.getCoordinates()
|
||||
@@ -229,8 +244,8 @@
|
||||
label: datasets.speed.label,
|
||||
data: trackPointsAndStatistics.points.map((point, index) => {
|
||||
return {
|
||||
x: trackPointsAndStatistics.statistics.distance[index],
|
||||
y: trackPointsAndStatistics.statistics.speed[index]
|
||||
x: getConvertedDistance(trackPointsAndStatistics.statistics.distance[index]),
|
||||
y: getConvertedVelocity(trackPointsAndStatistics.statistics.speed[index])
|
||||
};
|
||||
}),
|
||||
normalized: true,
|
||||
@@ -241,7 +256,7 @@
|
||||
label: datasets.hr.label,
|
||||
data: trackPointsAndStatistics.points.map((point, index) => {
|
||||
return {
|
||||
x: trackPointsAndStatistics.statistics.distance[index],
|
||||
x: getConvertedDistance(trackPointsAndStatistics.statistics.distance[index]),
|
||||
y: point.getHeartRate()
|
||||
};
|
||||
}),
|
||||
@@ -253,7 +268,7 @@
|
||||
label: datasets.cad.label,
|
||||
data: trackPointsAndStatistics.points.map((point, index) => {
|
||||
return {
|
||||
x: trackPointsAndStatistics.statistics.distance[index],
|
||||
x: getConvertedDistance(trackPointsAndStatistics.statistics.distance[index]),
|
||||
y: point.getCadence()
|
||||
};
|
||||
}),
|
||||
@@ -265,8 +280,8 @@
|
||||
label: datasets.atemp.label,
|
||||
data: trackPointsAndStatistics.points.map((point, index) => {
|
||||
return {
|
||||
x: trackPointsAndStatistics.statistics.distance[index],
|
||||
y: point.getTemperature()
|
||||
x: getConvertedDistance(trackPointsAndStatistics.statistics.distance[index]),
|
||||
y: getConvertedTemperature(point.getTemperature())
|
||||
};
|
||||
}),
|
||||
normalized: true,
|
||||
@@ -277,7 +292,7 @@
|
||||
label: datasets.power.label,
|
||||
data: trackPointsAndStatistics.points.map((point, index) => {
|
||||
return {
|
||||
x: trackPointsAndStatistics.statistics.distance[index],
|
||||
x: getConvertedDistance(trackPointsAndStatistics.statistics.distance[index]),
|
||||
y: point.getPower()
|
||||
};
|
||||
}),
|
||||
@@ -286,7 +301,8 @@
|
||||
hidden: true
|
||||
};
|
||||
chart.options.scales.x['min'] = 0;
|
||||
chart.options.scales.x['max'] = gpxFiles.statistics.distance.total;
|
||||
chart.options.scales.x['max'] = getConvertedDistance(gpxFiles.statistics.distance.total);
|
||||
|
||||
chart.update();
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import Tooltip from '$lib/components/Tooltip.svelte';
|
||||
import WithUnits from '$lib/components/WithUnits.svelte';
|
||||
|
||||
import { GPXStatistics } from 'gpx';
|
||||
|
||||
@@ -17,17 +18,6 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function toHHMMSS(seconds: number) {
|
||||
var hours = Math.floor(seconds / 3600);
|
||||
var minutes = Math.floor(seconds / 60) % 60;
|
||||
var seconds = Math.round(seconds % 60);
|
||||
|
||||
return [hours, minutes, seconds]
|
||||
.map((v) => (v < 10 ? '0' + v : v))
|
||||
.filter((v, i) => v !== '00' || i > 0)
|
||||
.join(':');
|
||||
}
|
||||
</script>
|
||||
|
||||
<Card.Root class="h-full overflow-hidden border-none min-w-48 pl-4">
|
||||
@@ -35,30 +25,32 @@
|
||||
<Tooltip>
|
||||
<span slot="data" class="flex flex-row items-center">
|
||||
<Ruler size="18" class="mr-1" />
|
||||
{gpxData.distance.total.toFixed(2)} km
|
||||
<WithUnits value={gpxData.distance.total} type="distance" />
|
||||
</span>
|
||||
<span slot="tooltip">Distance</span>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<span slot="data" class="flex flex-row items-center">
|
||||
<MoveUpRight size="18" class="mr-1" />
|
||||
{gpxData.elevation.gain.toFixed(0)} m
|
||||
<WithUnits value={gpxData.elevation.gain} type="elevation" />
|
||||
<MoveDownRight size="18" class="mx-1" />
|
||||
{gpxData.elevation.loss.toFixed(0)} m
|
||||
<WithUnits value={gpxData.elevation.loss} type="elevation" />
|
||||
</span>
|
||||
<span slot="tooltip">Elevation</span>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<span slot="data" class="flex flex-row items-center">
|
||||
<Zap size="18" class="mr-1" />
|
||||
{gpxData.speed.moving.toFixed(2)} km/h
|
||||
<WithUnits value={gpxData.speed.moving} type="speed" />
|
||||
</span>
|
||||
<span slot="tooltip">Speed</span>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<span slot="data" class="flex flex-row items-center">
|
||||
<Timer size="18" class="mr-1" />
|
||||
{toHHMMSS(gpxData.time.moving)} / {toHHMMSS(gpxData.time.total)}
|
||||
<WithUnits value={gpxData.time.moving} type="time" />
|
||||
<span class="mx-1">/</span>
|
||||
<WithUnits value={gpxData.time.total} type="time" />
|
||||
</span>
|
||||
<span slot="tooltip">Moving time / Total time</span>
|
||||
</Tooltip>
|
||||
|
@@ -23,11 +23,12 @@
|
||||
removeAllFiles,
|
||||
removeSelectedFiles,
|
||||
triggerFileInput,
|
||||
selectFiles
|
||||
selectFiles,
|
||||
settings
|
||||
} from '$lib/stores';
|
||||
|
||||
let distanceUnits = 'metric';
|
||||
let velocityUnits = 'speed';
|
||||
import { _ } from 'svelte-i18n';
|
||||
|
||||
let showDistanceMarkers = false;
|
||||
let showDirectionMarkers = false;
|
||||
</script>
|
||||
@@ -39,10 +40,12 @@
|
||||
<Logo class="h-5 mt-0.5 mx-2" />
|
||||
<Menubar.Root class="border-none h-fit p-0">
|
||||
<Menubar.Menu>
|
||||
<Menubar.Trigger>File</Menubar.Trigger>
|
||||
<Menubar.Trigger>{$_('menu.file')}</Menubar.Trigger>
|
||||
<Menubar.Content>
|
||||
<Menubar.Item>
|
||||
<Plus size="16" class="mr-1" /> New <Menubar.Shortcut>⌘N</Menubar.Shortcut>
|
||||
<Plus size="16" class="mr-1" />
|
||||
{$_('menu.new')}
|
||||
<Menubar.Shortcut>⌘N</Menubar.Shortcut>
|
||||
</Menubar.Item>
|
||||
<Menubar.Separator />
|
||||
<Menubar.Item on:click={triggerFileInput}>
|
||||
@@ -108,7 +111,7 @@
|
||||
><Menubar.Sub>
|
||||
<Menubar.SubTrigger inset>Distance units</Menubar.SubTrigger>
|
||||
<Menubar.SubContent>
|
||||
<Menubar.RadioGroup bind:value={distanceUnits}>
|
||||
<Menubar.RadioGroup bind:value={$settings.distanceUnits}>
|
||||
<Menubar.RadioItem value="metric">Metric</Menubar.RadioItem>
|
||||
<Menubar.RadioItem value="imperial">Imperial</Menubar.RadioItem>
|
||||
</Menubar.RadioGroup>
|
||||
@@ -117,12 +120,21 @@
|
||||
<Menubar.Sub>
|
||||
<Menubar.SubTrigger inset>Velocity units</Menubar.SubTrigger>
|
||||
<Menubar.SubContent>
|
||||
<Menubar.RadioGroup bind:value={velocityUnits}>
|
||||
<Menubar.RadioGroup bind:value={$settings.velocityUnits}>
|
||||
<Menubar.RadioItem value="speed">Speed</Menubar.RadioItem>
|
||||
<Menubar.RadioItem value="pace">Pace</Menubar.RadioItem>
|
||||
</Menubar.RadioGroup>
|
||||
</Menubar.SubContent>
|
||||
</Menubar.Sub>
|
||||
<Menubar.Sub>
|
||||
<Menubar.SubTrigger inset>Temperature units</Menubar.SubTrigger>
|
||||
<Menubar.SubContent>
|
||||
<Menubar.RadioGroup bind:value={$settings.temperatureUnits}>
|
||||
<Menubar.RadioItem value="celsius">Celsius</Menubar.RadioItem>
|
||||
<Menubar.RadioItem value="fahrenheit">Fahrenheit</Menubar.RadioItem>
|
||||
</Menubar.RadioGroup>
|
||||
</Menubar.SubContent>
|
||||
</Menubar.Sub>
|
||||
<Menubar.Separator />
|
||||
<Menubar.CheckboxItem bind:checked={showDistanceMarkers}>
|
||||
Show distance markers
|
||||
|
51
website/src/lib/components/WithUnits.svelte
Normal file
51
website/src/lib/components/WithUnits.svelte
Normal file
@@ -0,0 +1,51 @@
|
||||
<script lang="ts">
|
||||
import { settings } from '$lib/stores';
|
||||
import {
|
||||
celsiusToFahrenheit,
|
||||
distancePerHourToSecondsPerDistance,
|
||||
kilometersToMiles,
|
||||
metersToFeet,
|
||||
secondsToHHMMSS
|
||||
} from '$lib/units';
|
||||
|
||||
import { _ } from 'svelte-i18n';
|
||||
|
||||
export let value: number;
|
||||
export let type: 'distance' | 'elevation' | 'speed' | 'temperature' | 'time';
|
||||
</script>
|
||||
|
||||
{#if type === 'distance'}
|
||||
{#if $settings.distanceUnits === 'metric'}
|
||||
{value.toFixed(2)} {$_('units.kilometers')}
|
||||
{:else}
|
||||
{kilometersToMiles(value).toFixed(2)} {$_('units.miles')}
|
||||
{/if}
|
||||
{:else if type === 'elevation'}
|
||||
{#if $settings.distanceUnits === 'metric'}
|
||||
{value.toFixed(0)} {$_('units.meters')}
|
||||
{:else}
|
||||
{metersToFeet(value).toFixed(0)} {$_('units.feet')}
|
||||
{/if}
|
||||
{:else if type === 'speed'}
|
||||
{#if $settings.distanceUnits === 'metric'}
|
||||
{#if $settings.velocityUnits === 'speed'}
|
||||
{value.toFixed(2)} {$_('units.kilometers_per_hour')}
|
||||
{:else}
|
||||
{secondsToHHMMSS(distancePerHourToSecondsPerDistance(value))}
|
||||
{$_('units.minutes_per_kilometer')}
|
||||
{/if}
|
||||
{:else if $settings.velocityUnits === 'speed'}
|
||||
{kilometersToMiles(value).toFixed(2)} {$_('units.miles_per_hour')}
|
||||
{:else}
|
||||
{secondsToHHMMSS(distancePerHourToSecondsPerDistance(kilometersToMiles(value)))}
|
||||
{$_('units.minutes_per_mile')}
|
||||
{/if}
|
||||
{:else if type === 'temperature'}
|
||||
{#if $settings.temperatureUnits === 'celsius'}
|
||||
{value} {$_('units.celsius')}
|
||||
{:else}
|
||||
{celsiusToFahrenheit(value)} {$_('units.fahrenheit')}
|
||||
{/if}
|
||||
{:else if type === 'time'}
|
||||
{secondsToHHMMSS(value)}
|
||||
{/if}
|
@@ -8,6 +8,11 @@ export const fileCollection = writable<GPXFiles>(new GPXFiles([]));
|
||||
export const fileOrder = writable<GPXFile[]>([]);
|
||||
export const selectedFiles = writable<Set<GPXFile>>(new Set());
|
||||
export const selectFiles = writable<{ [key: string]: (file?: GPXFile) => void }>({});
|
||||
export const settings = writable<{ [key: string]: any }>({
|
||||
distanceUnits: 'metric',
|
||||
velocityUnits: 'speed',
|
||||
temperatureUnits: 'celsius',
|
||||
});
|
||||
|
||||
export function addFile(file: GPXFile) {
|
||||
fileCollection.update($files => {
|
||||
|
144
website/src/lib/units.ts
Normal file
144
website/src/lib/units.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import { get } from 'svelte/store';
|
||||
import { settings } from './stores';
|
||||
import { _ } from 'svelte-i18n';
|
||||
|
||||
export function kilometersToMiles(value: number) {
|
||||
return value * 0.621371;
|
||||
}
|
||||
|
||||
export function metersToFeet(value: number) {
|
||||
return value * 3.28084;
|
||||
}
|
||||
|
||||
export function celsiusToFahrenheit(value: number) {
|
||||
return value * 1.8 + 32;
|
||||
}
|
||||
|
||||
export function distancePerHourToSecondsPerDistance(value: number) {
|
||||
return 3600 / value;
|
||||
}
|
||||
|
||||
export function secondsToHHMMSS(value: number) {
|
||||
var hours = Math.floor(value / 3600);
|
||||
var minutes = Math.floor(value / 60) % 60;
|
||||
var seconds = Math.round(value % 60);
|
||||
|
||||
return [hours, minutes, seconds]
|
||||
.map((v) => (v < 10 ? '0' + v : v))
|
||||
.filter((v, i) => v !== '00' || i > 0)
|
||||
.join(':');
|
||||
}
|
||||
|
||||
// Get a string representation of the value with units
|
||||
export function getDistanceWithUnits(value: number, convert: boolean = true) {
|
||||
if (convert) {
|
||||
return getConvertedDistance(value).toFixed(2) + ' ' + getDistanceUnits();
|
||||
} else {
|
||||
return value.toFixed(2) + ' ' + getDistanceUnits();
|
||||
}
|
||||
}
|
||||
|
||||
export function getVelocityWithUnits(value: number, convert: boolean = true) {
|
||||
const velocityUnits = get(settings).velocityUnits;
|
||||
const distanceUnits = get(settings).distanceUnits;
|
||||
if (velocityUnits === 'speed') {
|
||||
if (convert) {
|
||||
return getConvertedVelocity(value).toFixed(2) + ' ' + getVelocityUnits();
|
||||
} else {
|
||||
return value.toFixed(2) + ' ' + getVelocityUnits();
|
||||
}
|
||||
} else {
|
||||
if (convert) {
|
||||
return secondsToHHMMSS(getConvertedVelocity(value));
|
||||
} else {
|
||||
return secondsToHHMMSS(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getElevationWithUnits(value: number, convert: boolean = true) {
|
||||
if (convert) {
|
||||
return getConvertedElevation(value).toFixed(0) + ' ' + getElevationUnits();
|
||||
} else {
|
||||
return value.toFixed(0) + ' ' + getElevationUnits();
|
||||
}
|
||||
}
|
||||
|
||||
export function getHeartRateWithUnits(value: number) {
|
||||
return value.toFixed(0) + ' ' + getHeartRateUnits();
|
||||
}
|
||||
|
||||
export function getCadenceWithUnits(value: number) {
|
||||
return value.toFixed(0) + ' ' + getCadenceUnits();
|
||||
}
|
||||
|
||||
export function getPowerWithUnits(value: number) {
|
||||
return value.toFixed(0) + ' ' + getPowerUnits();
|
||||
}
|
||||
|
||||
export function getTemperatureWithUnits(value: number, convert: boolean = true) {
|
||||
if (convert) {
|
||||
return getConvertedTemperature(value).toFixed(0) + ' ' + getTemperatureUnits();
|
||||
} else {
|
||||
return value.toFixed(0) + ' ' + getTemperatureUnits();
|
||||
}
|
||||
}
|
||||
|
||||
// Get the units
|
||||
export function getDistanceUnits() {
|
||||
return get(settings).distanceUnits === 'metric' ? get(_)('units.kilometers') : get(_)('units.miles');
|
||||
}
|
||||
|
||||
export function getVelocityUnits() {
|
||||
const velocityUnits = get(settings).velocityUnits;
|
||||
const distanceUnits = get(settings).distanceUnits;
|
||||
if (velocityUnits === 'speed') {
|
||||
return distanceUnits === 'metric' ? get(_)('units.kilometers_per_hour') : get(_)('units.miles_per_hour');
|
||||
} else {
|
||||
return distanceUnits === 'metric' ? get(_)('units.minutes_per_kilometer') : get(_)('units.minutes_per_mile');
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export function getElevationUnits() {
|
||||
return get(settings).distanceUnits === 'metric' ? get(_)('units.meters') : get(_)('units.feet');
|
||||
}
|
||||
|
||||
export function getHeartRateUnits() {
|
||||
return get(_)('units.heartrate');
|
||||
}
|
||||
|
||||
export function getCadenceUnits() {
|
||||
return get(_)('units.cadence');
|
||||
}
|
||||
|
||||
export function getPowerUnits() {
|
||||
return get(_)('units.power');
|
||||
}
|
||||
|
||||
export function getTemperatureUnits() {
|
||||
return get(settings).temperatureUnits === 'celsius' ? get(_)('units.celsius') : get(_)('units.fahrenheit');
|
||||
}
|
||||
|
||||
// Convert only the value
|
||||
export function getConvertedDistance(value: number) {
|
||||
return get(settings).distanceUnits === 'metric' ? value : kilometersToMiles(value);
|
||||
}
|
||||
|
||||
export function getConvertedElevation(value: number) {
|
||||
return get(settings).distanceUnits === 'metric' ? value : metersToFeet(value);
|
||||
}
|
||||
|
||||
export function getConvertedVelocity(value: number) {
|
||||
const velocityUnits = get(settings).velocityUnits;
|
||||
const distanceUnits = get(settings).distanceUnits;
|
||||
if (velocityUnits === 'speed') {
|
||||
return distanceUnits === 'metric' ? value : kilometersToMiles(value);
|
||||
} else {
|
||||
return distanceUnits === 'metric' ? distancePerHourToSecondsPerDistance(value) : distancePerHourToSecondsPerDistance(kilometersToMiles(value));
|
||||
}
|
||||
}
|
||||
|
||||
export function getConvertedTemperature(value: number) {
|
||||
return get(settings).temperatureUnits === 'celsius' ? value : celsiusToFahrenheit(value);
|
||||
}
|
33
website/src/locales/en.json
Normal file
33
website/src/locales/en.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"menu": {
|
||||
"file": "File",
|
||||
"new": "New"
|
||||
},
|
||||
"quantities": {
|
||||
"distance": "Distance",
|
||||
"elevation": "Elevation",
|
||||
"temperature": "Temperature",
|
||||
"speed": "Speed",
|
||||
"pace": "Pace",
|
||||
"heartrate": "Heart rate",
|
||||
"cadence": "Cadence",
|
||||
"power": "Power",
|
||||
"slope": "Slope",
|
||||
"surface": "Surface"
|
||||
},
|
||||
"units": {
|
||||
"meters": "m",
|
||||
"feet": "ft",
|
||||
"kilometers": "km",
|
||||
"miles": "mi",
|
||||
"celsius": "°C",
|
||||
"fahrenheit": "°F",
|
||||
"kilometers_per_hour": "km/h",
|
||||
"miles_per_hour": "mph",
|
||||
"minutes_per_kilometer": "min/km",
|
||||
"minutes_per_mile": "min/mi",
|
||||
"heartrate": "bpm",
|
||||
"cadence": "rpm",
|
||||
"power": "W"
|
||||
}
|
||||
}
|
10
website/src/routes/+layout.js
Normal file
10
website/src/routes/+layout.js
Normal file
@@ -0,0 +1,10 @@
|
||||
export const prerender = true;
|
||||
|
||||
import { register, init } from 'svelte-i18n';
|
||||
|
||||
register('en', () => import('../locales/en.json'));
|
||||
|
||||
init({
|
||||
fallbackLocale: 'en',
|
||||
initialLocale: 'en',
|
||||
});
|
@@ -1 +0,0 @@
|
||||
export const prerender = true;
|
@@ -1,25 +1,5 @@
|
||||
<script lang="ts">
|
||||
import Data from '$lib/components/Data.svelte';
|
||||
import ElevationProfile from '$lib/components/ElevationProfile.svelte';
|
||||
import FileList from '$lib/components/FileList.svelte';
|
||||
import GPXData from '$lib/components/GPXData.svelte';
|
||||
import Map from '$lib/components/Map.svelte';
|
||||
import Menu from '$lib/components/Menu.svelte';
|
||||
import Toolbar from '$lib/components/toolbar/Toolbar.svelte';
|
||||
import LayerControl from '$lib/components/layer-control/LayerControl.svelte';
|
||||
import App from '$lib/components/App.svelte';
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col w-screen h-screen">
|
||||
<div class="grow relative">
|
||||
<Menu />
|
||||
<Toolbar />
|
||||
<Map class="h-full" />
|
||||
<LayerControl />
|
||||
<Data />
|
||||
<FileList />
|
||||
</div>
|
||||
<div class="h-60 flex flex-row gap-2 overflow-hidden border">
|
||||
<GPXData />
|
||||
<ElevationProfile />
|
||||
</div>
|
||||
</div>
|
||||
<App />
|
||||
|
10
website/src/routes/[language]/+page.svelte
Normal file
10
website/src/routes/[language]/+page.svelte
Normal file
@@ -0,0 +1,10 @@
|
||||
<script lang="ts">
|
||||
import App from '$lib/components/App.svelte';
|
||||
|
||||
import { locale } from 'svelte-i18n';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
locale.set($page.params.language);
|
||||
</script>
|
||||
|
||||
<App />
|
Reference in New Issue
Block a user