mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-10-16 04:18:19 +00:00
prettier config + format all, closes #175
This commit is contained in:
@@ -1,267 +1,271 @@
|
||||
<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 Map from '$lib/components/Map.svelte';
|
||||
import LayerControl from '$lib/components/layer-control/LayerControl.svelte';
|
||||
import OpenIn from '$lib/components/embedding/OpenIn.svelte';
|
||||
import {
|
||||
gpxStatistics,
|
||||
slicedGPXStatistics,
|
||||
embedding,
|
||||
loadFile,
|
||||
map,
|
||||
updateGPXData
|
||||
} 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,
|
||||
getFilesFromEmbeddingOptions,
|
||||
type EmbeddingOptions
|
||||
} from './Embedding';
|
||||
import { mode, setMode } from 'mode-watcher';
|
||||
import { browser } from '$app/environment';
|
||||
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 Map from '$lib/components/Map.svelte';
|
||||
import LayerControl from '$lib/components/layer-control/LayerControl.svelte';
|
||||
import OpenIn from '$lib/components/embedding/OpenIn.svelte';
|
||||
import {
|
||||
gpxStatistics,
|
||||
slicedGPXStatistics,
|
||||
embedding,
|
||||
loadFile,
|
||||
map,
|
||||
updateGPXData,
|
||||
} 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,
|
||||
getFilesFromEmbeddingOptions,
|
||||
type EmbeddingOptions,
|
||||
} from './Embedding';
|
||||
import { mode, setMode } from 'mode-watcher';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
$embedding = true;
|
||||
$embedding = true;
|
||||
|
||||
const {
|
||||
currentBasemap,
|
||||
distanceUnits,
|
||||
velocityUnits,
|
||||
temperatureUnits,
|
||||
fileOrder,
|
||||
distanceMarkers,
|
||||
directionMarkers
|
||||
} = settings;
|
||||
const {
|
||||
currentBasemap,
|
||||
distanceUnits,
|
||||
velocityUnits,
|
||||
temperatureUnits,
|
||||
fileOrder,
|
||||
distanceMarkers,
|
||||
directionMarkers,
|
||||
} = settings;
|
||||
|
||||
export let useHash = true;
|
||||
export let options: EmbeddingOptions;
|
||||
export let hash: string;
|
||||
export let useHash = true;
|
||||
export let options: EmbeddingOptions;
|
||||
export let hash: string;
|
||||
|
||||
let prevSettings = {
|
||||
distanceMarkers: false,
|
||||
directionMarkers: false,
|
||||
distanceUnits: 'metric',
|
||||
velocityUnits: 'speed',
|
||||
temperatureUnits: 'celsius',
|
||||
theme: 'system'
|
||||
};
|
||||
let prevSettings = {
|
||||
distanceMarkers: false,
|
||||
directionMarkers: false,
|
||||
distanceUnits: 'metric',
|
||||
velocityUnits: 'speed',
|
||||
temperatureUnits: 'celsius',
|
||||
theme: 'system',
|
||||
};
|
||||
|
||||
function applyOptions() {
|
||||
fileObservers.update(($fileObservers) => {
|
||||
$fileObservers.clear();
|
||||
return $fileObservers;
|
||||
});
|
||||
function applyOptions() {
|
||||
fileObservers.update(($fileObservers) => {
|
||||
$fileObservers.clear();
|
||||
return $fileObservers;
|
||||
});
|
||||
|
||||
let downloads: Promise<GPXFile | null>[] = [];
|
||||
getFilesFromEmbeddingOptions(options).forEach((url) => {
|
||||
downloads.push(
|
||||
fetch(url)
|
||||
.then((response) => response.blob())
|
||||
.then((blob) => new File([blob], url.split('/').pop() ?? url))
|
||||
.then(loadFile)
|
||||
);
|
||||
});
|
||||
let downloads: Promise<GPXFile | null>[] = [];
|
||||
getFilesFromEmbeddingOptions(options).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
|
||||
}
|
||||
};
|
||||
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;
|
||||
}
|
||||
fileObservers.update(($fileObservers) => {
|
||||
files.forEach((file, index) => {
|
||||
if (file === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let id = `gpx-${index}-embed`;
|
||||
file._data.id = id;
|
||||
let statistics = new GPXStatisticsTree(file);
|
||||
let id = `gpx-${index}-embed`;
|
||||
file._data.id = id;
|
||||
let statistics = new GPXStatisticsTree(file);
|
||||
|
||||
$fileObservers.set(
|
||||
id,
|
||||
readable({
|
||||
file,
|
||||
statistics
|
||||
})
|
||||
);
|
||||
$fileObservers.set(
|
||||
id,
|
||||
readable({
|
||||
file,
|
||||
statistics,
|
||||
})
|
||||
);
|
||||
|
||||
ids.push(id);
|
||||
let fileBounds = statistics.getStatisticsFor(new ListFileItem(id)).global.bounds;
|
||||
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);
|
||||
});
|
||||
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;
|
||||
});
|
||||
return $fileObservers;
|
||||
});
|
||||
|
||||
$fileOrder = [...$fileOrder.filter((id) => !id.includes('embed')), ...ids];
|
||||
$fileOrder = [...$fileOrder.filter((id) => !id.includes('embed')), ...ids];
|
||||
|
||||
selection.update(($selection) => {
|
||||
$selection.clear();
|
||||
ids.forEach((id) => {
|
||||
$selection.toggle(new ListFileItem(id));
|
||||
});
|
||||
return $selection;
|
||||
});
|
||||
selection.update(($selection) => {
|
||||
$selection.clear();
|
||||
ids.forEach((id) => {
|
||||
$selection.toggle(new ListFileItem(id));
|
||||
});
|
||||
return $selection;
|
||||
});
|
||||
|
||||
if (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 (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 !== $currentBasemap && allowedEmbeddingBasemaps.includes(options.basemap)) {
|
||||
$currentBasemap = options.basemap;
|
||||
}
|
||||
if (
|
||||
options.basemap !== $currentBasemap &&
|
||||
allowedEmbeddingBasemaps.includes(options.basemap)
|
||||
) {
|
||||
$currentBasemap = options.basemap;
|
||||
}
|
||||
|
||||
if (options.distanceMarkers !== $distanceMarkers) {
|
||||
$distanceMarkers = options.distanceMarkers;
|
||||
}
|
||||
if (options.distanceMarkers !== $distanceMarkers) {
|
||||
$distanceMarkers = options.distanceMarkers;
|
||||
}
|
||||
|
||||
if (options.directionMarkers !== $directionMarkers) {
|
||||
$directionMarkers = options.directionMarkers;
|
||||
}
|
||||
if (options.directionMarkers !== $directionMarkers) {
|
||||
$directionMarkers = options.directionMarkers;
|
||||
}
|
||||
|
||||
if (options.distanceUnits !== $distanceUnits) {
|
||||
$distanceUnits = options.distanceUnits;
|
||||
}
|
||||
if (options.distanceUnits !== $distanceUnits) {
|
||||
$distanceUnits = options.distanceUnits;
|
||||
}
|
||||
|
||||
if (options.velocityUnits !== $velocityUnits) {
|
||||
$velocityUnits = options.velocityUnits;
|
||||
}
|
||||
if (options.velocityUnits !== $velocityUnits) {
|
||||
$velocityUnits = options.velocityUnits;
|
||||
}
|
||||
|
||||
if (options.temperatureUnits !== $temperatureUnits) {
|
||||
$temperatureUnits = options.temperatureUnits;
|
||||
}
|
||||
if (options.temperatureUnits !== $temperatureUnits) {
|
||||
$temperatureUnits = options.temperatureUnits;
|
||||
}
|
||||
|
||||
if (options.theme !== $mode) {
|
||||
setMode(options.theme);
|
||||
}
|
||||
}
|
||||
if (options.theme !== $mode) {
|
||||
setMode(options.theme);
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
prevSettings.distanceMarkers = $distanceMarkers;
|
||||
prevSettings.directionMarkers = $directionMarkers;
|
||||
prevSettings.distanceUnits = $distanceUnits;
|
||||
prevSettings.velocityUnits = $velocityUnits;
|
||||
prevSettings.temperatureUnits = $temperatureUnits;
|
||||
prevSettings.theme = $mode ?? 'system';
|
||||
});
|
||||
onMount(() => {
|
||||
prevSettings.distanceMarkers = $distanceMarkers;
|
||||
prevSettings.directionMarkers = $directionMarkers;
|
||||
prevSettings.distanceUnits = $distanceUnits;
|
||||
prevSettings.velocityUnits = $velocityUnits;
|
||||
prevSettings.temperatureUnits = $temperatureUnits;
|
||||
prevSettings.theme = $mode ?? 'system';
|
||||
});
|
||||
|
||||
$: if (browser && options) {
|
||||
applyOptions();
|
||||
}
|
||||
$: if (browser && options) {
|
||||
applyOptions();
|
||||
}
|
||||
|
||||
$: if ($fileOrder) {
|
||||
updateGPXData();
|
||||
}
|
||||
$: if ($fileOrder) {
|
||||
updateGPXData();
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
if ($distanceMarkers !== prevSettings.distanceMarkers) {
|
||||
$distanceMarkers = prevSettings.distanceMarkers;
|
||||
}
|
||||
onDestroy(() => {
|
||||
if ($distanceMarkers !== prevSettings.distanceMarkers) {
|
||||
$distanceMarkers = prevSettings.distanceMarkers;
|
||||
}
|
||||
|
||||
if ($directionMarkers !== prevSettings.directionMarkers) {
|
||||
$directionMarkers = prevSettings.directionMarkers;
|
||||
}
|
||||
if ($directionMarkers !== prevSettings.directionMarkers) {
|
||||
$directionMarkers = prevSettings.directionMarkers;
|
||||
}
|
||||
|
||||
if ($distanceUnits !== prevSettings.distanceUnits) {
|
||||
$distanceUnits = prevSettings.distanceUnits;
|
||||
}
|
||||
if ($distanceUnits !== prevSettings.distanceUnits) {
|
||||
$distanceUnits = prevSettings.distanceUnits;
|
||||
}
|
||||
|
||||
if ($velocityUnits !== prevSettings.velocityUnits) {
|
||||
$velocityUnits = prevSettings.velocityUnits;
|
||||
}
|
||||
if ($velocityUnits !== prevSettings.velocityUnits) {
|
||||
$velocityUnits = prevSettings.velocityUnits;
|
||||
}
|
||||
|
||||
if ($temperatureUnits !== prevSettings.temperatureUnits) {
|
||||
$temperatureUnits = prevSettings.temperatureUnits;
|
||||
}
|
||||
if ($temperatureUnits !== prevSettings.temperatureUnits) {
|
||||
$temperatureUnits = prevSettings.temperatureUnits;
|
||||
}
|
||||
|
||||
if ($mode !== prevSettings.theme) {
|
||||
setMode(prevSettings.theme);
|
||||
}
|
||||
if ($mode !== prevSettings.theme) {
|
||||
setMode(prevSettings.theme);
|
||||
}
|
||||
|
||||
$selection.clear();
|
||||
$fileObservers.clear();
|
||||
$fileOrder = $fileOrder.filter((id) => !id.includes('embed'));
|
||||
});
|
||||
$selection.clear();
|
||||
$fileObservers.clear();
|
||||
$fileOrder = $fileOrder.filter((id) => !id.includes('embed'));
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="absolute flex flex-col h-full w-full border rounded-xl overflow-clip">
|
||||
<div class="grow relative">
|
||||
<Map
|
||||
class="h-full {$fileObservers.size > 1 ? 'horizontal' : ''}"
|
||||
accessToken={options.token}
|
||||
geocoder={false}
|
||||
geolocate={false}
|
||||
hash={useHash}
|
||||
/>
|
||||
<OpenIn bind:files={options.files} bind:ids={options.ids} />
|
||||
<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}
|
||||
showControls={options.elevation.controls}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="grow relative">
|
||||
<Map
|
||||
class="h-full {$fileObservers.size > 1 ? 'horizontal' : ''}"
|
||||
accessToken={options.token}
|
||||
geocoder={false}
|
||||
geolocate={false}
|
||||
hash={useHash}
|
||||
/>
|
||||
<OpenIn bind:files={options.files} bind:ids={options.ids} />
|
||||
<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}
|
||||
showControls={options.elevation.controls}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -39,14 +39,14 @@ export const defaultEmbeddingOptions = {
|
||||
hr: false,
|
||||
cad: false,
|
||||
temp: false,
|
||||
power: false
|
||||
power: false,
|
||||
},
|
||||
distanceMarkers: false,
|
||||
directionMarkers: false,
|
||||
distanceUnits: 'metric',
|
||||
velocityUnits: 'speed',
|
||||
temperatureUnits: 'celsius',
|
||||
theme: 'system'
|
||||
theme: 'system',
|
||||
};
|
||||
|
||||
export function getDefaultEmbeddingOptions(): EmbeddingOptions {
|
||||
@@ -59,7 +59,11 @@ export function getMergedEmbeddingOptions(
|
||||
): EmbeddingOptions {
|
||||
const mergedOptions = JSON.parse(JSON.stringify(defaultOptions));
|
||||
for (const key in options) {
|
||||
if (typeof options[key] === 'object' && options[key] !== null && !Array.isArray(options[key])) {
|
||||
if (
|
||||
typeof options[key] === 'object' &&
|
||||
options[key] !== null &&
|
||||
!Array.isArray(options[key])
|
||||
) {
|
||||
mergedOptions[key] = getMergedEmbeddingOptions(options[key], defaultOptions[key]);
|
||||
} else {
|
||||
mergedOptions[key] = options[key];
|
||||
@@ -79,7 +83,10 @@ export function getCleanedEmbeddingOptions(
|
||||
cleanedOptions[key] !== null &&
|
||||
!Array.isArray(cleanedOptions[key])
|
||||
) {
|
||||
cleanedOptions[key] = getCleanedEmbeddingOptions(cleanedOptions[key], defaultOptions[key]);
|
||||
cleanedOptions[key] = getCleanedEmbeddingOptions(
|
||||
cleanedOptions[key],
|
||||
defaultOptions[key]
|
||||
);
|
||||
if (Object.keys(cleanedOptions[key]).length === 0) {
|
||||
delete cleanedOptions[key];
|
||||
}
|
||||
@@ -141,7 +148,7 @@ export function convertOldEmbeddingOptions(options: URLSearchParams): any {
|
||||
}
|
||||
if (options.has('slope')) {
|
||||
newOptions.elevation = {
|
||||
fill: 'slope'
|
||||
fill: 'slope',
|
||||
};
|
||||
}
|
||||
return newOptions;
|
||||
|
@@ -1,328 +1,339 @@
|
||||
<script lang="ts">
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
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 {
|
||||
Zap,
|
||||
HeartPulse,
|
||||
Orbit,
|
||||
Thermometer,
|
||||
SquareActivity,
|
||||
Coins,
|
||||
Milestone,
|
||||
Video
|
||||
} 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';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
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 {
|
||||
Zap,
|
||||
HeartPulse,
|
||||
Orbit,
|
||||
Thermometer,
|
||||
SquareActivity,
|
||||
Coins,
|
||||
Milestone,
|
||||
Video,
|
||||
} 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 = getDefaultEmbeddingOptions();
|
||||
options.token = 'YOUR_MAPBOX_TOKEN';
|
||||
options.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];
|
||||
$: {
|
||||
let urls = files.split(',');
|
||||
urls = urls.filter((url) => url.length > 0);
|
||||
if (JSON.stringify(urls) !== JSON.stringify(options.files)) {
|
||||
options.files = urls;
|
||||
}
|
||||
}
|
||||
let driveIds = '';
|
||||
$: {
|
||||
let ids = driveIds.split(',');
|
||||
ids = ids.filter((id) => id.length > 0);
|
||||
if (JSON.stringify(ids) !== JSON.stringify(options.ids)) {
|
||||
options.ids = ids;
|
||||
}
|
||||
}
|
||||
let files = options.files[0];
|
||||
$: {
|
||||
let urls = files.split(',');
|
||||
urls = urls.filter((url) => url.length > 0);
|
||||
if (JSON.stringify(urls) !== JSON.stringify(options.files)) {
|
||||
options.files = urls;
|
||||
}
|
||||
}
|
||||
let driveIds = '';
|
||||
$: {
|
||||
let ids = driveIds.split(',');
|
||||
ids = ids.filter((id) => id.length > 0);
|
||||
if (JSON.stringify(ids) !== JSON.stringify(options.ids)) {
|
||||
options.ids = ids;
|
||||
}
|
||||
}
|
||||
|
||||
let manualCamera = false;
|
||||
let manualCamera = false;
|
||||
|
||||
let zoom = '0';
|
||||
let lat = '0';
|
||||
let lon = '0';
|
||||
let bearing = '0';
|
||||
let pitch = '0';
|
||||
let zoom = '0';
|
||||
let lat = '0';
|
||||
let lon = '0';
|
||||
let bearing = '0';
|
||||
let pitch = '0';
|
||||
|
||||
$: hash = manualCamera ? `#${zoom}/${lat}/${lon}/${bearing}/${pitch}` : '';
|
||||
$: hash = manualCamera ? `#${zoom}/${lat}/${lon}/${bearing}/${pitch}` : '';
|
||||
|
||||
$: iframeOptions =
|
||||
options.token.length === 0 || options.token === 'YOUR_MAPBOX_TOKEN'
|
||||
? Object.assign({}, options, { token: PUBLIC_MAPBOX_TOKEN })
|
||||
: options;
|
||||
$: iframeOptions =
|
||||
options.token.length === 0 || options.token === 'YOUR_MAPBOX_TOKEN'
|
||||
? Object.assign({}, options, { token: PUBLIC_MAPBOX_TOKEN })
|
||||
: options;
|
||||
|
||||
async function resizeMap() {
|
||||
if ($map) {
|
||||
await tick();
|
||||
$map.resize();
|
||||
}
|
||||
}
|
||||
async function resizeMap() {
|
||||
if ($map) {
|
||||
await tick();
|
||||
$map.resize();
|
||||
}
|
||||
}
|
||||
|
||||
$: if (options.elevation.height || options.elevation.show) {
|
||||
resizeMap();
|
||||
}
|
||||
$: if (options.elevation.height || options.elevation.show) {
|
||||
resizeMap();
|
||||
}
|
||||
|
||||
function updateCamera() {
|
||||
if ($map) {
|
||||
let center = $map.getCenter();
|
||||
lat = center.lat.toFixed(4);
|
||||
lon = center.lng.toFixed(4);
|
||||
zoom = $map.getZoom().toFixed(2);
|
||||
bearing = $map.getBearing().toFixed(1);
|
||||
pitch = $map.getPitch().toFixed(0);
|
||||
}
|
||||
}
|
||||
function updateCamera() {
|
||||
if ($map) {
|
||||
let center = $map.getCenter();
|
||||
lat = center.lat.toFixed(4);
|
||||
lon = center.lng.toFixed(4);
|
||||
zoom = $map.getZoom().toFixed(2);
|
||||
bearing = $map.getBearing().toFixed(1);
|
||||
pitch = $map.getPitch().toFixed(0);
|
||||
}
|
||||
}
|
||||
|
||||
$: if ($map) {
|
||||
$map.on('moveend', updateCamera);
|
||||
}
|
||||
$: if ($map) {
|
||||
$map.on('moveend', updateCamera);
|
||||
}
|
||||
</script>
|
||||
|
||||
<Card.Root id="embedding-playground">
|
||||
<Card.Header>
|
||||
<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="drive_ids">{$_('embedding.drive_ids')}</Label>
|
||||
<Input id="drive_ids" type="text" class="h-8" bind:value={driveIds} />
|
||||
<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-full h-8">
|
||||
<Select.Value />
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-60 overflow-y-scroll">
|
||||
{#each allowedEmbeddingBasemaps as basemap}
|
||||
<Select.Item value={basemap}>{$_(`layers.label.${basemap}`)}</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<Label for="profile">{$_('menu.elevation_profile')}</Label>
|
||||
<Checkbox id="profile" bind:checked={options.elevation.show} />
|
||||
</div>
|
||||
{#if options.elevation.show}
|
||||
<div class="grid grid-cols-2 gap-x-6 gap-y-3 rounded-md border p-3 mt-1">
|
||||
<Label class="flex flex-row items-center gap-2">
|
||||
{$_('embedding.height')}
|
||||
<Input type="number" bind:value={options.elevation.height} class="h-8 w-20" />
|
||||
</Label>
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<span class="shrink-0">
|
||||
{$_('embedding.fill_by')}
|
||||
</span>
|
||||
<Select.Root
|
||||
selected={{ value: 'none', label: $_('embedding.none') }}
|
||||
onSelectedChange={(selected) => {
|
||||
let value = selected?.value;
|
||||
if (value === 'none') {
|
||||
options.elevation.fill = undefined;
|
||||
} else if (value === 'slope' || value === 'surface' || value === 'highway') {
|
||||
options.elevation.fill = value;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Select.Trigger class="grow h-8">
|
||||
<Select.Value />
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
<Select.Item value="slope">{$_('quantities.slope')}</Select.Item>
|
||||
<Select.Item value="surface">{$_('quantities.surface')}</Select.Item>
|
||||
<Select.Item value="highway">{$_('quantities.highway')}</Select.Item>
|
||||
<Select.Item value="none">{$_('embedding.none')}</Select.Item>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<Checkbox id="controls" bind:checked={options.elevation.controls} />
|
||||
<Label for="controls">{$_('embedding.show_controls')}</Label>
|
||||
</div>
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<Checkbox id="show-speed" bind:checked={options.elevation.speed} />
|
||||
<Label for="show-speed" class="flex flex-row items-center gap-1">
|
||||
<Zap size="16" />
|
||||
{$_('quantities.speed')}
|
||||
</Label>
|
||||
</div>
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<Checkbox id="show-hr" bind:checked={options.elevation.hr} />
|
||||
<Label for="show-hr" class="flex flex-row items-center gap-1">
|
||||
<HeartPulse size="16" />
|
||||
{$_('quantities.heartrate')}
|
||||
</Label>
|
||||
</div>
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<Checkbox id="show-cad" bind:checked={options.elevation.cad} />
|
||||
<Label for="show-cad" class="flex flex-row items-center gap-1">
|
||||
<Orbit size="16" />
|
||||
{$_('quantities.cadence')}
|
||||
</Label>
|
||||
</div>
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<Checkbox id="show-temp" bind:checked={options.elevation.temp} />
|
||||
<Label for="show-temp" class="flex flex-row items-center gap-1">
|
||||
<Thermometer size="16" />
|
||||
{$_('quantities.temperature')}
|
||||
</Label>
|
||||
</div>
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<Checkbox id="show-power" bind:checked={options.elevation.power} />
|
||||
<Label for="show-power" class="flex flex-row items-center gap-1">
|
||||
<SquareActivity size="16" />
|
||||
{$_('quantities.power')}
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<Checkbox id="distance-markers" bind:checked={options.distanceMarkers} />
|
||||
<Label for="distance-markers" class="flex flex-row items-center gap-1">
|
||||
<Coins size="16" />
|
||||
{$_('menu.distance_markers')}
|
||||
</Label>
|
||||
</div>
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<Checkbox id="direction-markers" bind:checked={options.directionMarkers} />
|
||||
<Label for="direction-markers" class="flex flex-row items-center gap-1">
|
||||
<Milestone size="16" />
|
||||
{$_('menu.direction_markers')}
|
||||
</Label>
|
||||
</div>
|
||||
<div class="flex flex-row flex-wrap justify-between gap-3">
|
||||
<Label class="flex flex-col items-start gap-2">
|
||||
{$_('menu.distance_units')}
|
||||
<RadioGroup.Root bind:value={options.distanceUnits}>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item value="metric" id="metric" />
|
||||
<Label for="metric">{$_('menu.metric')}</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item value="imperial" id="imperial" />
|
||||
<Label for="imperial">{$_('menu.imperial')}</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item value="nautical" id="nautical" />
|
||||
<Label for="nautical">{$_('menu.nautical')}</Label>
|
||||
</div>
|
||||
</RadioGroup.Root>
|
||||
</Label>
|
||||
<Label class="flex flex-col items-start gap-2">
|
||||
{$_('menu.velocity_units')}
|
||||
<RadioGroup.Root bind:value={options.velocityUnits}>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item value="speed" id="speed" />
|
||||
<Label for="speed">{$_('quantities.speed')}</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item value="pace" id="pace" />
|
||||
<Label for="pace">{$_('quantities.pace')}</Label>
|
||||
</div>
|
||||
</RadioGroup.Root>
|
||||
</Label>
|
||||
<Label class="flex flex-col items-start gap-2">
|
||||
{$_('menu.temperature_units')}
|
||||
<RadioGroup.Root bind:value={options.temperatureUnits}>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item value="celsius" id="celsius" />
|
||||
<Label for="celsius">{$_('menu.celsius')}</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item value="fahrenheit" id="fahrenheit" />
|
||||
<Label for="fahrenheit">{$_('menu.fahrenheit')}</Label>
|
||||
</div>
|
||||
</RadioGroup.Root>
|
||||
</Label>
|
||||
</div>
|
||||
<Label class="flex flex-col items-start gap-2">
|
||||
{$_('menu.mode')}
|
||||
<RadioGroup.Root bind:value={options.theme} class="flex flex-row">
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item value="system" id="system" />
|
||||
<Label for="system">{$_('menu.system')}</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item value="light" id="light" />
|
||||
<Label for="light">{$_('menu.light')}</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item value="dark" id="dark" />
|
||||
<Label for="dark">{$_('menu.dark')}</Label>
|
||||
</div>
|
||||
</RadioGroup.Root>
|
||||
</Label>
|
||||
<div class="flex flex-col gap-3 p-3 border rounded-md">
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<Checkbox id="manual-camera" bind:checked={manualCamera} />
|
||||
<Label for="manual-camera" class="flex flex-row items-center gap-1">
|
||||
<Video size="16" />
|
||||
{$_('embedding.manual_camera')}
|
||||
</Label>
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
{$_('embedding.manual_camera_description')}
|
||||
</p>
|
||||
<div class="flex flex-row flex-wrap items-center gap-6">
|
||||
<Label class="flex flex-col gap-1">
|
||||
<span>{$_('embedding.latitude')}</span>
|
||||
<span>{lat}</span>
|
||||
</Label>
|
||||
<Label class="flex flex-col gap-1">
|
||||
<span>{$_('embedding.longitude')}</span>
|
||||
<span>{lon}</span>
|
||||
</Label>
|
||||
<Label class="flex flex-col gap-1">
|
||||
<span>{$_('embedding.zoom')}</span>
|
||||
<span>{zoom}</span>
|
||||
</Label>
|
||||
<Label class="flex flex-col gap-1">
|
||||
<span>{$_('embedding.bearing')}</span>
|
||||
<span>{bearing}</span>
|
||||
</Label>
|
||||
<Label class="flex flex-col gap-1">
|
||||
<span>{$_('embedding.pitch')}</span>
|
||||
<span>{pitch}</span>
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
<Label>
|
||||
{$_('embedding.preview')}
|
||||
</Label>
|
||||
<div class="relative h-[600px]">
|
||||
<Embedding bind:options={iframeOptions} bind:hash useHash={false} />
|
||||
</div>
|
||||
<Label>
|
||||
{$_('embedding.code')}
|
||||
</Label>
|
||||
<pre class="bg-primary text-primary-foreground p-3 rounded-md whitespace-normal break-all">
|
||||
<Card.Header>
|
||||
<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="drive_ids">{$_('embedding.drive_ids')}</Label>
|
||||
<Input id="drive_ids" type="text" class="h-8" bind:value={driveIds} />
|
||||
<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-full h-8">
|
||||
<Select.Value />
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-60 overflow-y-scroll">
|
||||
{#each allowedEmbeddingBasemaps as basemap}
|
||||
<Select.Item value={basemap}>{$_(`layers.label.${basemap}`)}</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<Label for="profile">{$_('menu.elevation_profile')}</Label>
|
||||
<Checkbox id="profile" bind:checked={options.elevation.show} />
|
||||
</div>
|
||||
{#if options.elevation.show}
|
||||
<div class="grid grid-cols-2 gap-x-6 gap-y-3 rounded-md border p-3 mt-1">
|
||||
<Label class="flex flex-row items-center gap-2">
|
||||
{$_('embedding.height')}
|
||||
<Input
|
||||
type="number"
|
||||
bind:value={options.elevation.height}
|
||||
class="h-8 w-20"
|
||||
/>
|
||||
</Label>
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<span class="shrink-0">
|
||||
{$_('embedding.fill_by')}
|
||||
</span>
|
||||
<Select.Root
|
||||
selected={{ value: 'none', label: $_('embedding.none') }}
|
||||
onSelectedChange={(selected) => {
|
||||
let value = selected?.value;
|
||||
if (value === 'none') {
|
||||
options.elevation.fill = undefined;
|
||||
} else if (
|
||||
value === 'slope' ||
|
||||
value === 'surface' ||
|
||||
value === 'highway'
|
||||
) {
|
||||
options.elevation.fill = value;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Select.Trigger class="grow h-8">
|
||||
<Select.Value />
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
<Select.Item value="slope">{$_('quantities.slope')}</Select.Item>
|
||||
<Select.Item value="surface">{$_('quantities.surface')}</Select.Item
|
||||
>
|
||||
<Select.Item value="highway">{$_('quantities.highway')}</Select.Item
|
||||
>
|
||||
<Select.Item value="none">{$_('embedding.none')}</Select.Item>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<Checkbox id="controls" bind:checked={options.elevation.controls} />
|
||||
<Label for="controls">{$_('embedding.show_controls')}</Label>
|
||||
</div>
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<Checkbox id="show-speed" bind:checked={options.elevation.speed} />
|
||||
<Label for="show-speed" class="flex flex-row items-center gap-1">
|
||||
<Zap size="16" />
|
||||
{$_('quantities.speed')}
|
||||
</Label>
|
||||
</div>
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<Checkbox id="show-hr" bind:checked={options.elevation.hr} />
|
||||
<Label for="show-hr" class="flex flex-row items-center gap-1">
|
||||
<HeartPulse size="16" />
|
||||
{$_('quantities.heartrate')}
|
||||
</Label>
|
||||
</div>
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<Checkbox id="show-cad" bind:checked={options.elevation.cad} />
|
||||
<Label for="show-cad" class="flex flex-row items-center gap-1">
|
||||
<Orbit size="16" />
|
||||
{$_('quantities.cadence')}
|
||||
</Label>
|
||||
</div>
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<Checkbox id="show-temp" bind:checked={options.elevation.temp} />
|
||||
<Label for="show-temp" class="flex flex-row items-center gap-1">
|
||||
<Thermometer size="16" />
|
||||
{$_('quantities.temperature')}
|
||||
</Label>
|
||||
</div>
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<Checkbox id="show-power" bind:checked={options.elevation.power} />
|
||||
<Label for="show-power" class="flex flex-row items-center gap-1">
|
||||
<SquareActivity size="16" />
|
||||
{$_('quantities.power')}
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<Checkbox id="distance-markers" bind:checked={options.distanceMarkers} />
|
||||
<Label for="distance-markers" class="flex flex-row items-center gap-1">
|
||||
<Coins size="16" />
|
||||
{$_('menu.distance_markers')}
|
||||
</Label>
|
||||
</div>
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<Checkbox id="direction-markers" bind:checked={options.directionMarkers} />
|
||||
<Label for="direction-markers" class="flex flex-row items-center gap-1">
|
||||
<Milestone size="16" />
|
||||
{$_('menu.direction_markers')}
|
||||
</Label>
|
||||
</div>
|
||||
<div class="flex flex-row flex-wrap justify-between gap-3">
|
||||
<Label class="flex flex-col items-start gap-2">
|
||||
{$_('menu.distance_units')}
|
||||
<RadioGroup.Root bind:value={options.distanceUnits}>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item value="metric" id="metric" />
|
||||
<Label for="metric">{$_('menu.metric')}</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item value="imperial" id="imperial" />
|
||||
<Label for="imperial">{$_('menu.imperial')}</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item value="nautical" id="nautical" />
|
||||
<Label for="nautical">{$_('menu.nautical')}</Label>
|
||||
</div>
|
||||
</RadioGroup.Root>
|
||||
</Label>
|
||||
<Label class="flex flex-col items-start gap-2">
|
||||
{$_('menu.velocity_units')}
|
||||
<RadioGroup.Root bind:value={options.velocityUnits}>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item value="speed" id="speed" />
|
||||
<Label for="speed">{$_('quantities.speed')}</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item value="pace" id="pace" />
|
||||
<Label for="pace">{$_('quantities.pace')}</Label>
|
||||
</div>
|
||||
</RadioGroup.Root>
|
||||
</Label>
|
||||
<Label class="flex flex-col items-start gap-2">
|
||||
{$_('menu.temperature_units')}
|
||||
<RadioGroup.Root bind:value={options.temperatureUnits}>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item value="celsius" id="celsius" />
|
||||
<Label for="celsius">{$_('menu.celsius')}</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item value="fahrenheit" id="fahrenheit" />
|
||||
<Label for="fahrenheit">{$_('menu.fahrenheit')}</Label>
|
||||
</div>
|
||||
</RadioGroup.Root>
|
||||
</Label>
|
||||
</div>
|
||||
<Label class="flex flex-col items-start gap-2">
|
||||
{$_('menu.mode')}
|
||||
<RadioGroup.Root bind:value={options.theme} class="flex flex-row">
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item value="system" id="system" />
|
||||
<Label for="system">{$_('menu.system')}</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item value="light" id="light" />
|
||||
<Label for="light">{$_('menu.light')}</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item value="dark" id="dark" />
|
||||
<Label for="dark">{$_('menu.dark')}</Label>
|
||||
</div>
|
||||
</RadioGroup.Root>
|
||||
</Label>
|
||||
<div class="flex flex-col gap-3 p-3 border rounded-md">
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<Checkbox id="manual-camera" bind:checked={manualCamera} />
|
||||
<Label for="manual-camera" class="flex flex-row items-center gap-1">
|
||||
<Video size="16" />
|
||||
{$_('embedding.manual_camera')}
|
||||
</Label>
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
{$_('embedding.manual_camera_description')}
|
||||
</p>
|
||||
<div class="flex flex-row flex-wrap items-center gap-6">
|
||||
<Label class="flex flex-col gap-1">
|
||||
<span>{$_('embedding.latitude')}</span>
|
||||
<span>{lat}</span>
|
||||
</Label>
|
||||
<Label class="flex flex-col gap-1">
|
||||
<span>{$_('embedding.longitude')}</span>
|
||||
<span>{lon}</span>
|
||||
</Label>
|
||||
<Label class="flex flex-col gap-1">
|
||||
<span>{$_('embedding.zoom')}</span>
|
||||
<span>{zoom}</span>
|
||||
</Label>
|
||||
<Label class="flex flex-col gap-1">
|
||||
<span>{$_('embedding.bearing')}</span>
|
||||
<span>{bearing}</span>
|
||||
</Label>
|
||||
<Label class="flex flex-col gap-1">
|
||||
<span>{$_('embedding.pitch')}</span>
|
||||
<span>{pitch}</span>
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
<Label>
|
||||
{$_('embedding.preview')}
|
||||
</Label>
|
||||
<div class="relative h-[600px]">
|
||||
<Embedding bind:options={iframeOptions} bind:hash useHash={false} />
|
||||
</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?options=${encodeURIComponent(JSON.stringify(getCleanedEmbeddingOptions(options)))}${hash}" width="100%" height="600px" frameborder="0" style="outline: none;"/>`}
|
||||
</code>
|
||||
</pre>
|
||||
</fieldset>
|
||||
</Card.Content>
|
||||
</fieldset>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
Reference in New Issue
Block a user