embedding progress

This commit is contained in:
vcoppe
2024-07-12 15:00:33 +02:00
parent cfeeabb580
commit d774f3a53c
14 changed files with 386 additions and 281 deletions

View File

@@ -31,6 +31,7 @@ jobs:
- name: Build website
env:
BASE_PATH: '/${{ github.event.repository.name }}'
PUBLIC_MAPBOX_TOKEN: ${{ secrets.PUBLIC_MAPBOX_TOKEN }}
run: |
npm run build --prefix website

1
website/.env.example Normal file
View File

@@ -0,0 +1 @@
PUBLIC_MAPBOX_TOKEN=YOUR_MAPBOX_TOKEN

View File

@@ -1,7 +1,6 @@
import { PUBLIC_MAPBOX_TOKEN } from '$env/static/public';
import { type AnySourceData, type Style } from 'mapbox-gl';
export const mapboxAccessToken = 'pk.eyJ1IjoidmNvcHBlIiwiYSI6ImNseG0zNHpwdTA1NXUycXF4ejJyODc0NWQifQ.4tiCPQ1SxnYl4o7aQc89VA';
export const basemaps: { [key: string]: string | Style; } = {
mapboxOutdoors: 'mapbox://styles/mapbox/outdoors-v12',
mapboxSatellite: 'mapbox://styles/mapbox/satellite-streets-v12',
@@ -269,7 +268,7 @@ export const basemaps: { [key: string]: string | Style; } = {
export function extendBasemap(basemap: string | Style): string | Style {
if (typeof basemap === 'object') {
basemap["glyphs"] = "mapbox://fonts/mapbox/{fontstack}/{range}.pbf";
basemap["sprite"] = `https://api.mapbox.com/styles/v1/mapbox/outdoors-v12/sprite?access_token=${mapboxAccessToken}`;
basemap["sprite"] = `https://api.mapbox.com/styles/v1/mapbox/outdoors-v12/sprite?access_token=${PUBLIC_MAPBOX_TOKEN}`;
}
return basemap;
}

View File

@@ -11,12 +11,13 @@
import { settings } from '$lib/db';
import { locale } from 'svelte-i18n';
import { get } from 'svelte/store';
import { mapboxAccessToken } from '$lib/assets/layers';
mapboxgl.accessToken = mapboxAccessToken;
import { PUBLIC_MAPBOX_TOKEN } from '$env/static/public';
export let accessToken = PUBLIC_MAPBOX_TOKEN;
export let geocoder = true;
mapboxgl.accessToken = accessToken;
let fitBoundsOptions: mapboxgl.FitBoundsOptions = {
maxZoom: 15,
linear: true,

View File

@@ -7,7 +7,7 @@
import { getURLForLanguage } from '$lib/utils';
</script>
<nav class="w-full sticky top-0 bg-background z-10">
<nav class="w-full sticky top-0 bg-background z-50">
<div class="mx-6 py-2 flex flex-row items-center border-b gap-4 sm:gap-8">
<a href={getURLForLanguage($locale, '/')} class="shrink-0 translate-y-0.5">
<Logo class="h-8 sm:hidden" iconOnly={true} />

View File

@@ -64,7 +64,12 @@
@apply px-1;
}
:global(.markdown a) {
:global(.markdown > a) {
@apply text-blue-500;
@apply hover:underline;
}
:global(.markdown p > a) {
@apply text-blue-500;
@apply hover:underline;
}

View File

@@ -0,0 +1,205 @@
<script lang="ts">
import GPXLayers from '$lib/components/gpx-layer/GPXLayers.svelte';
import ElevationProfile from '$lib/components/ElevationProfile.svelte';
import FileList from '$lib/components/file-list/FileList.svelte';
import GPXStatistics from '$lib/components/GPXStatistics.svelte';
import MapComponent from '$lib/components/Map.svelte';
import LayerControl from '$lib/components/layer-control/LayerControl.svelte';
import OpenIn from '$lib/components/OpenIn.svelte';
import { gpxStatistics, slicedGPXStatistics, embedding, loadFile, map } from '$lib/stores';
import { onDestroy, onMount } from 'svelte';
import { fileObservers, settings, GPXStatisticsTree } from '$lib/db';
import { readable } from 'svelte/store';
import type { GPXFile } from 'gpx';
import { selection } from '$lib/components/file-list/Selection';
import { ListFileItem } from '$lib/components/file-list/FileList';
import { allowedEmbeddingBasemaps, type EmbeddingOptions } from './Embedding';
import { languages } from '$lib/languages';
import { getURLForLanguage } from '$lib/utils';
$embedding = true;
const { currentBasemap, distanceUnits, velocityUnits, temperatureUnits } = settings;
export let options: EmbeddingOptions;
let prevUnits = {
distance: '',
velocity: '',
temperature: ''
};
function applyOptions() {
fileObservers.update(($fileObservers) => {
$fileObservers.clear();
return $fileObservers;
});
let downloads: Promise<GPXFile | null>[] = [];
options.files.forEach((url) => {
downloads.push(
fetch(url)
.then((response) => response.blob())
.then((blob) => new File([blob], url.split('/').pop() ?? url))
.then(loadFile)
);
});
Promise.all(downloads).then((files) => {
let ids: string[] = [];
let bounds = {
southWest: {
lat: 90,
lon: 180
},
northEast: {
lat: -90,
lon: -180
}
};
fileObservers.update(($fileObservers) => {
files.forEach((file, index) => {
if (file === null) {
return;
}
let id = `gpx-${index}`;
file._data.id = id;
let statistics = new GPXStatisticsTree(file);
$fileObservers.set(
id,
readable({
file,
statistics
})
);
ids.push(id);
let fileBounds = statistics.getStatisticsFor(new ListFileItem(id)).global.bounds;
bounds.southWest.lat = Math.min(bounds.southWest.lat, fileBounds.southWest.lat);
bounds.southWest.lon = Math.min(bounds.southWest.lon, fileBounds.southWest.lon);
bounds.northEast.lat = Math.max(bounds.northEast.lat, fileBounds.northEast.lat);
bounds.northEast.lon = Math.max(bounds.northEast.lon, fileBounds.northEast.lon);
});
return $fileObservers;
});
selection.update(($selection) => {
$selection.clear();
ids.forEach((id) => {
$selection.toggle(new ListFileItem(id));
});
return $selection;
});
map.subscribe(($map) => {
if ($map) {
$map.fitBounds(
[
bounds.southWest.lon,
bounds.southWest.lat,
bounds.northEast.lon,
bounds.northEast.lat
],
{
padding: 80,
linear: true,
easing: () => 1
}
);
}
});
});
if (options.basemap !== $currentBasemap && allowedEmbeddingBasemaps.includes(options.basemap)) {
$currentBasemap = options.basemap;
}
if (options.distanceUnits !== $distanceUnits) {
$distanceUnits = options.distanceUnits;
}
if (options.velocityUnits !== $velocityUnits) {
$velocityUnits = options.velocityUnits;
}
if (options.temperatureUnits !== $temperatureUnits) {
$temperatureUnits = options.temperatureUnits;
}
}
onMount(() => {
prevUnits.distance = $distanceUnits;
prevUnits.velocity = $velocityUnits;
prevUnits.temperature = $temperatureUnits;
});
$: if (options) {
applyOptions();
}
onDestroy(() => {
if ($distanceUnits !== prevUnits.distance) {
$distanceUnits = prevUnits.distance;
}
if ($velocityUnits !== prevUnits.velocity) {
$velocityUnits = prevUnits.velocity;
}
if ($temperatureUnits !== prevUnits.temperature) {
$temperatureUnits = prevUnits.temperature;
}
});
</script>
<div class="absolute flex flex-col h-full w-full border rounded-xl overflow-clip">
<div class="grow relative">
<MapComponent
class="h-full {$fileObservers.size > 1 ? 'horizontal' : ''}"
accessToken={options.token}
geocoder={false}
/>
<OpenIn bind:files={options.files} />
<LayerControl />
<GPXLayers />
{#if $fileObservers.size > 1}
<div class="h-10 -translate-y-10 w-full pointer-events-none absolute z-30">
<FileList orientation="horizontal" />
</div>
{/if}
</div>
<div
class="{options.elevation.show ? '' : 'h-10'} flex flex-row gap-2 px-2 sm:px-4"
style={options.elevation.show ? `height: ${options.elevation.height}px` : ''}
>
<GPXStatistics
{gpxStatistics}
{slicedGPXStatistics}
panelSize={options.elevation.height}
orientation={options.elevation.show ? 'vertical' : 'horizontal'}
/>
{#if options.elevation.show}
<ElevationProfile
{gpxStatistics}
{slicedGPXStatistics}
additionalDatasets={[
options.elevation.speed ? 'speed' : null,
options.elevation.hr ? 'hr' : null,
options.elevation.cad ? 'cad' : null,
options.elevation.temp ? 'temp' : null,
options.elevation.power ? 'power' : null
].filter((dataset) => dataset !== null)}
elevationFill={options.elevation.fill}
panelSize={options.elevation.height}
showControls={options.elevation.controls}
class="py-2"
/>
{/if}
</div>
</div>

View File

@@ -0,0 +1,63 @@
export type EmbeddingOptions = {
token: string;
files: string[];
basemap: string;
elevation: {
show: boolean;
height: number,
controls: boolean,
fill: 'slope' | 'surface' | undefined,
speed: boolean,
hr: boolean,
cad: boolean,
temp: boolean,
power: boolean,
},
distanceUnits: 'metric' | 'imperial',
velocityUnits: 'speed' | 'pace',
temperatureUnits: 'celsius' | 'fahrenheit',
};
export const defaultEmbeddingOptions = {
token: '',
files: [],
basemap: 'mapboxOutdoors',
elevation: {
show: true,
height: 170,
controls: true,
fill: undefined,
speed: false,
hr: false,
cad: false,
temp: false,
power: false,
},
distanceUnits: 'metric',
velocityUnits: 'speed',
temperatureUnits: 'celsius',
};
export function getDefaultEmbeddingOptions(): EmbeddingOptions {
return JSON.parse(JSON.stringify(defaultEmbeddingOptions));
}
export function getCleanedEmbeddingOptions(options: any, defaultOptions: any = defaultEmbeddingOptions): any {
const cleanedOptions = JSON.parse(JSON.stringify(options));
for (const key in cleanedOptions) {
if (typeof cleanedOptions[key] === 'object' && cleanedOptions[key] !== null && !Array.isArray(cleanedOptions[key])) {
cleanedOptions[key] = getCleanedEmbeddingOptions(cleanedOptions[key], defaultOptions[key]);
if (Object.keys(cleanedOptions[key]).length === 0) {
delete cleanedOptions[key];
}
} else if (JSON.stringify(cleanedOptions[key]) === JSON.stringify(defaultOptions[key])) {
delete cleanedOptions[key];
}
}
return cleanedOptions;
}
export const allowedEmbeddingBasemaps = [
'mapboxOutdoors',
'mapboxSatellite',
];

View File

@@ -5,42 +5,32 @@
import * as Select from '$lib/components/ui/select';
import { Checkbox } from '$lib/components/ui/checkbox';
import * as RadioGroup from '$lib/components/ui/radio-group';
import { basemaps } from '$lib/assets/layers';
import { Zap, HeartPulse, Orbit, Thermometer, SquareActivity } from 'lucide-svelte';
import { _ } from 'svelte-i18n';
import {
allowedEmbeddingBasemaps,
getCleanedEmbeddingOptions,
getDefaultEmbeddingOptions
} from './Embedding';
import { PUBLIC_MAPBOX_TOKEN } from '$env/static/public';
import Embedding from './Embedding.svelte';
import { map } from '$lib/stores';
import { tick } from 'svelte';
import { base } from '$app/paths';
let options = {
files: ['https://raw.githubusercontent.com/gpxstudio/gpx.studio/main/gpx/test-data/simple.gpx'],
basemap: 'mapboxOutdoors',
elevation: {
show: true,
height: 170,
data: [],
fill: undefined,
controls: true
},
distanceUnits: 'metric',
velocityUnits: 'speed',
temperatureUnits: 'celsius'
};
let files =
'https://raw.githubusercontent.com/gpxstudio/gpx.studio/main/gpx/test-data/simple.gpx';
let options = getDefaultEmbeddingOptions();
options.token = 'YOUR_MAPBOX_TOKEN';
options.files = [
'https://raw.githubusercontent.com/gpxstudio/gpx.studio/main/gpx/test-data/simple.gpx'
];
let files = options.files[0];
$: if (files) {
options.files = files.split(',');
let urls = files.split(',');
urls = urls.filter((url) => url.length > 0);
if (JSON.stringify(urls) !== JSON.stringify(options.files)) {
options.files = urls;
}
let additionalData = {
speed: false,
hr: false,
cad: false,
temp: false,
power: false
};
$: if (additionalData) {
options.elevation.data = Object.keys(additionalData).filter((key) => additionalData[key]);
}
let manualCamera = false;
@@ -53,36 +43,47 @@
$: hash = manualCamera ? `#${zoom}/${lat}/${lon}/${bearing}/${pitch}` : '';
$: console.log(options);
</script>
$: iframeOptions =
options.token.length === 0 || options.token === 'YOUR_MAPBOX_TOKEN'
? Object.assign({}, options, { token: PUBLIC_MAPBOX_TOKEN })
: options;
<iframe
src={`../../embed?options=${encodeURIComponent(JSON.stringify(options))}${hash}`}
style="width: 100%; height: 600px;"
></iframe>
async function resizeMap() {
if ($map) {
await tick();
$map.resize();
}
}
$: if (options.elevation.height || options.elevation.show) {
resizeMap();
}
</script>
<Card.Root>
<Card.Header>
<Card.Title>Card Title</Card.Title>
<Card.Description>Card Description</Card.Description>
<Card.Title>{$_('embedding.title')}</Card.Title>
</Card.Header>
<Card.Content>
<fieldset class="flex flex-col gap-3">
<Label for="token">{$_('embedding.mapbox_token')}</Label>
<Input id="token" type="text" class="h-8" bind:value={options.token} />
<Label for="file_urls">{$_('embedding.file_urls')}</Label>
<Input id="file_urls" type="text" class="h-8" bind:value={files} />
<Label for="basemap">{$_('embedding.basemap')}</Label>
<Select.Root
selected={{ value: options.basemap, label: $_(`layers.label.${options.basemap}`) }}
onSelectedChange={(selected) => {
if (selected?.value) {
options.basemap = selected?.value;
}
}}
>
<Select.Trigger id="basemap" class="w-[180px] h-8">
<Select.Trigger id="basemap" class="w-full h-8">
<Select.Value />
</Select.Trigger>
<Select.Content class="max-h-60 overflow-y-scroll">
{#each Object.keys(basemaps) as basemap}
{#each allowedEmbeddingBasemaps as basemap}
<Select.Item value={basemap}>{$_(`layers.label.${basemap}`)}</Select.Item>
{/each}
</Select.Content>
@@ -102,13 +103,13 @@
{$_('embedding.fill_by')}
</span>
<Select.Root
selected={{ value: 'none', label: $_('embedding.none') }}
onSelectedChange={(selected) => {
if (selected?.value) {
if (selected?.value === 'none') {
let value = selected?.value;
if (value === 'none') {
options.elevation.fill = undefined;
} else {
options.elevation.fill = selected?.value;
}
} else if (value === 'slope' || value === 'surface') {
options.elevation.fill = value;
}
}}
>
@@ -127,35 +128,35 @@
<Label for="controls">{$_('embedding.show_controls')}</Label>
</div>
<div class="flex flex-row items-center gap-2">
<Checkbox id="show-speed" bind:checked={additionalData.speed} />
<Checkbox id="show-speed" bind:checked={options.elevation.speed} />
<Label for="show-speed" class="flex flex-row items-center gap-1">
<Zap size="16" />
{$_('chart.show_speed')}
</Label>
</div>
<div class="flex flex-row items-center gap-2">
<Checkbox id="show-hr" bind:checked={additionalData.hr} />
<Checkbox id="show-hr" bind:checked={options.elevation.hr} />
<Label for="show-hr" class="flex flex-row items-center gap-1">
<HeartPulse size="16" />
{$_('chart.show_heartrate')}
</Label>
</div>
<div class="flex flex-row items-center gap-2">
<Checkbox id="show-cad" bind:checked={additionalData.cad} />
<Checkbox id="show-cad" bind:checked={options.elevation.cad} />
<Label for="show-cad" class="flex flex-row items-center gap-1">
<Orbit size="16" />
{$_('chart.show_cadence')}
</Label>
</div>
<div class="flex flex-row items-center gap-2">
<Checkbox id="show-temp" bind:checked={additionalData.temp} />
<Checkbox id="show-temp" bind:checked={options.elevation.temp} />
<Label for="show-temp" class="flex flex-row items-center gap-1">
<Thermometer size="16" />
{$_('chart.show_temperature')}
</Label>
</div>
<div class="flex flex-row items-center gap-2">
<Checkbox id="show-power" bind:checked={additionalData.power} />
<Checkbox id="show-power" bind:checked={options.elevation.power} />
<Label for="show-power" class="flex flex-row items-center gap-1">
<SquareActivity size="16" />
{$_('chart.show_power')}
@@ -204,6 +205,20 @@
</RadioGroup.Root>
</Label>
</div>
<Label>
{$_('embedding.preview')}
</Label>
<div class="relative h-[600px]">
<Embedding bind:options={iframeOptions} />
</div>
<Label>
{$_('embedding.code')}
</Label>
<pre class="bg-primary text-primary-foreground p-3 rounded-md whitespace-normal break-all">
<code class="language-html">
{`<iframe src="https://gpx.studio${base}/embed?${JSON.stringify(getCleanedEmbeddingOptions(options))}${hash}" width="100%" height="600px" frameborder="0" />`}
</code>
</pre>
</fieldset>
</Card.Content>
</Card.Root>

View File

@@ -1,7 +1,6 @@
<script lang="ts">
import { map, gpxLayers } from '$lib/stores';
import { GPXLayer } from './GPXLayer';
import { get } from 'svelte/store';
import WaypointPopup from './WaypointPopup.svelte';
import { fileObservers } from '$lib/db';
import { DistanceMarkers } from './DistanceMarkers';
@@ -21,14 +20,14 @@
// add layers for new files
$fileObservers.forEach((file, fileId) => {
if (!gpxLayers.has(fileId)) {
gpxLayers.set(fileId, new GPXLayer(get(map), fileId, file));
gpxLayers.set(fileId, new GPXLayer($map, fileId, file));
}
});
}
$: if ($map) {
distanceMarkers = new DistanceMarkers(get(map));
startEndMarkers = new StartEndMarkers(get(map));
distanceMarkers = new DistanceMarkers($map);
startEndMarkers = new StartEndMarkers($map);
}
</script>

View File

@@ -3,9 +3,14 @@ title: Integration
---
<script>
import EmbeddingPlaygound from '$lib/components/EmbeddingPlayground.svelte';
import DocsNote from '$lib/components/docs/DocsNote.svelte';
import EmbeddingPlaygound from '$lib/components/embedding/EmbeddingPlayground.svelte';
</script>
# { title }
<DocsNote>
This section is a work in progress.
</DocsNote>
<EmbeddingPlaygound />

View File

@@ -372,6 +372,8 @@
"identity_description": "The website is free to use, without ads, and the source code is publicly available on GitHub. This is only possible thanks to the incredible support of the community."
},
"embedding": {
"title": "Create your own map",
"mapbox_token": "Mapbox access token",
"file_urls": "File URLs (separated by commas)",
"basemap": "Basemap",
"height": "Height",
@@ -384,6 +386,8 @@
"latitude": "Latitude",
"longitude": "Longitude",
"pitch": "Pitch",
"bearing": "Bearing"
"bearing": "Bearing",
"preview": "Preview",
"code": "Integration code"
}
}

View File

@@ -15,6 +15,8 @@
import { gpxStatistics, loadFiles, slicedGPXStatistics } from '$lib/stores';
import { onMount } from 'svelte';
import { page } from '$app/stores';
import { languages } from '$lib/languages';
import { getURLForLanguage } from '$lib/utils';
const {
verticalFileView,
@@ -99,6 +101,15 @@
{/if}
</div>
<!-- hidden links for svelte crawling -->
<div class="hidden">
{#each Object.entries(languages) as [lang, label]}
<a href={getURLForLanguage(lang, '/embed')}>
{label}
</a>
{/each}
</div>
<style lang="postcss">
div :global(.toaster.group) {
@apply absolute;

View File

@@ -1,38 +1,13 @@
<script lang="ts">
import GPXLayers from '$lib/components/gpx-layer/GPXLayers.svelte';
import ElevationProfile from '$lib/components/ElevationProfile.svelte';
import FileList from '$lib/components/file-list/FileList.svelte';
import GPXStatistics from '$lib/components/GPXStatistics.svelte';
import MapComponent from '$lib/components/Map.svelte';
import LayerControl from '$lib/components/layer-control/LayerControl.svelte';
import OpenIn from '$lib/components/OpenIn.svelte';
import { gpxStatistics, slicedGPXStatistics, embedding, loadFile, map } from '$lib/stores';
import { page } from '$app/stores';
import { onDestroy, onMount } from 'svelte';
import { fileObservers, settings, GPXStatisticsTree } from '$lib/db';
import { readable } from 'svelte/store';
import type { GPXFile } from 'gpx';
import { selection } from '$lib/components/file-list/Selection';
import { ListFileItem } from '$lib/components/file-list/FileList';
import { onMount } from 'svelte';
import Embedding from '$lib/components/embedding/Embedding.svelte';
import {
getDefaultEmbeddingOptions,
type EmbeddingOptions
} from '$lib/components/embedding/Embedding';
$embedding = true;
const { currentBasemap, distanceUnits, velocityUnits, temperatureUnits } = settings;
let elevationProfile = true;
let bottomPanelSize = 170;
let additionalDatasets: string[] = [];
let elevationFill: 'slope' | 'surface' | undefined = undefined;
let elevationControls = true;
let files: string[] = [];
let prevUnits = {
distance: '',
velocity: '',
temperature: ''
};
let embeddingOptions: EmbeddingOptions | undefined = undefined;
onMount(() => {
let options = $page.url.searchParams.get('options');
@@ -43,189 +18,10 @@
if (options === null) {
return;
}
if (options.files && Array.isArray(options.files)) {
files = options.files;
let downloads: Promise<GPXFile | null>[] = [];
options.files.forEach((url) => {
downloads.push(
fetch(url)
.then((response) => response.blob())
.then((blob) => new File([blob], url.split('/').pop()))
.then(loadFile)
);
});
Promise.all(downloads).then((files) => {
let ids: string[] = [];
let bounds = {
southWest: {
lat: 90,
lon: 180
},
northEast: {
lat: -90,
lon: -180
}
};
files.forEach((file, index) => {
if (file === null) {
return;
}
let id = `gpx-${index}`;
file._data.id = id;
let statistics = new GPXStatisticsTree(file);
fileObservers.update(($fileObservers) => {
$fileObservers.set(
id,
readable({
file,
statistics
})
);
return $fileObservers;
});
ids.push(id);
let fileBounds = statistics.getStatisticsFor(new ListFileItem(id)).global.bounds;
bounds.southWest.lat = Math.min(bounds.southWest.lat, fileBounds.southWest.lat);
bounds.southWest.lon = Math.min(bounds.southWest.lon, fileBounds.southWest.lon);
bounds.northEast.lat = Math.max(bounds.northEast.lat, fileBounds.northEast.lat);
bounds.northEast.lon = Math.max(bounds.northEast.lon, fileBounds.northEast.lon);
});
selection.update(($selection) => {
$selection.clear();
ids.forEach((id) => {
$selection.toggle(new ListFileItem(id));
});
return $selection;
});
if ($page.url.hash.length === 0) {
map.subscribe(($map) => {
if ($map) {
$map.fitBounds(
[
bounds.southWest.lon,
bounds.southWest.lat,
bounds.northEast.lon,
bounds.northEast.lat
],
{
padding: 80,
linear: true,
easing: () => 1
}
);
}
});
}
});
}
if (options.basemap !== undefined && typeof options.basemap === 'string') {
currentBasemap.set(options.basemap);
}
if (options.elevation !== undefined && typeof options.elevation === 'object') {
const elevationOptions = options.elevation;
if (elevationOptions.show !== undefined && typeof elevationOptions.show === 'boolean') {
elevationProfile = elevationOptions.show;
}
if (elevationOptions.data && Array.isArray(elevationOptions.data)) {
elevationOptions.data.forEach((dataset) => {
if (['speed', 'hr', 'cad', 'temp', 'power'].includes(dataset)) {
additionalDatasets.push(dataset);
}
});
}
if (elevationOptions.fill === 'slope' || elevationOptions.fill === 'surface') {
elevationFill = elevationOptions.fill;
}
if (elevationOptions.height !== undefined && typeof elevationOptions.height === 'number') {
bottomPanelSize = elevationOptions.height;
}
if (
elevationOptions.controls !== undefined &&
typeof elevationOptions.controls === 'boolean'
) {
elevationControls = elevationOptions.controls;
}
}
prevUnits.distance = $distanceUnits;
prevUnits.velocity = $velocityUnits;
prevUnits.temperature = $temperatureUnits;
if (options.distanceUnits === 'metric' || options.distanceUnits === 'imperial') {
$distanceUnits = options.distanceUnits;
}
if (options.velocityUnits === 'speed' || options.velocityUnits === 'pace') {
$velocityUnits = options.velocityUnits;
}
if (options.temperatureUnits === 'celsius' || options.temperatureUnits === 'fahrenheit') {
$temperatureUnits = options.temperatureUnits;
}
});
onDestroy(() => {
if ($distanceUnits !== prevUnits.distance) {
$distanceUnits = prevUnits.distance;
}
if ($velocityUnits !== prevUnits.velocity) {
$velocityUnits = prevUnits.velocity;
}
if ($temperatureUnits !== prevUnits.temperature) {
$temperatureUnits = prevUnits.temperature;
}
embeddingOptions = Object.assign(getDefaultEmbeddingOptions(), options);
});
</script>
<div class="fixed flex flex-col h-full w-full border rounded-xl overflow-clip">
<div class="grow relative">
<MapComponent class="h-full {$fileObservers.size > 1 ? 'horizontal' : ''}" geocoder={false} />
<OpenIn bind:files />
<LayerControl />
<GPXLayers />
{#if $fileObservers.size > 1}
<div class="h-10 -translate-y-10 w-full pointer-events-none absolute z-30">
<FileList orientation="horizontal" />
</div>
{#if embeddingOptions}
<Embedding options={embeddingOptions} />
{/if}
</div>
<div
class="{elevationProfile ? '' : 'h-10'} flex flex-row gap-2 px-2 sm:px-4"
style={elevationProfile ? `height: ${bottomPanelSize}px` : ''}
>
<GPXStatistics
{gpxStatistics}
{slicedGPXStatistics}
panelSize={bottomPanelSize}
orientation={elevationProfile ? 'vertical' : 'horizontal'}
/>
{#if elevationProfile}
<ElevationProfile
{gpxStatistics}
{slicedGPXStatistics}
{additionalDatasets}
{elevationFill}
panelSize={bottomPanelSize}
showControls={elevationControls}
class="py-2"
/>
{/if}
</div>
</div>