diff --git a/website/src/lib/components/App.svelte b/website/src/lib/components/App.svelte deleted file mode 100644 index 7e02ec8f..00000000 --- a/website/src/lib/components/App.svelte +++ /dev/null @@ -1,91 +0,0 @@ - - -
-
-
- -
- -
- - - - - - {#if !$verticalFileView} -
- -
- {/if} -
- {#if $elevationProfile} - - {/if} -
- - {#if $elevationProfile} - - {/if} -
-
- {#if $verticalFileView} - - - {/if} -
- - diff --git a/website/src/lib/components/ElevationProfile.svelte b/website/src/lib/components/ElevationProfile.svelte index afe82818..c38cfbe5 100644 --- a/website/src/lib/components/ElevationProfile.svelte +++ b/website/src/lib/components/ElevationProfile.svelte @@ -39,15 +39,16 @@ import type { Writable } from 'svelte/store'; import { DateFormatter } from '@internationalized/date'; import type { GPXStatistics } from 'gpx'; + import { settings } from '$lib/db'; export let gpxStatistics: Writable; export let slicedGPXStatistics: Writable<[GPXStatistics, number, number] | undefined>; - export let distanceUnits: 'metric' | 'imperial'; - export let velocityUnits: 'speed' | 'pace'; - export let temperatureUnits: 'celsius' | 'fahrenheit'; export let panelSize: number; export let additionalDatasets: string[]; export let elevationFill: 'slope' | 'surface' | undefined; + export let showControls: boolean = true; + + const { distanceUnits, velocityUnits, temperatureUnits } = settings; let df: DateFormatter; @@ -61,7 +62,7 @@ let canvas: HTMLCanvasElement; let showAdditionalScales = true; let updateShowAdditionalScales = () => { - showAdditionalScales = canvas.width >= 1200; + showAdditionalScales = canvas.width >= 600; }; let overlay: HTMLCanvasElement; let chart: Chart; @@ -135,7 +136,7 @@ } return `${$_('quantities.elevation')}: ${getElevationWithUnits(point.y, false)}`; } else if (context.datasetIndex === 1) { - return `${velocityUnits === 'speed' ? $_('quantities.speed') : $_('quantities.pace')}: ${getVelocityWithUnits(point.y, false)}`; + return `${$velocityUnits === 'speed' ? $_('quantities.speed') : $_('quantities.pace')}: ${getVelocityWithUnits(point.y, false)}`; } else if (context.datasetIndex === 2) { return `${$_('quantities.heartrate')}: ${getHeartRateWithUnits(point.y)}`; } else if (context.datasetIndex === 3) { @@ -194,7 +195,7 @@ } = { speed: { id: 'speed', - getLabel: () => (velocityUnits === 'speed' ? $_('quantities.speed') : $_('quantities.pace')), + getLabel: () => ($velocityUnits === 'speed' ? $_('quantities.speed') : $_('quantities.pace')), getUnits: () => getVelocityUnits() }, hr: { @@ -234,13 +235,13 @@ grid: { display: false }, - reverse: () => id === 'speed' && velocityUnits === 'pace', + reverse: () => id === 'speed' && $velocityUnits === 'pace', display: false }; } options.scales.yspeed['ticks'] = { callback: function (value: number) { - if (velocityUnits === 'speed') { + if ($velocityUnits === 'speed') { return value; } else { return secondsToHHMMSS(value); @@ -342,7 +343,7 @@ canvas.addEventListener('pointerup', onMouseUp); }); - $: if (chart && distanceUnits && velocityUnits && temperatureUnits) { + $: if (chart && $distanceUnits && $velocityUnits && $temperatureUnits) { let data = $gpxStatistics; // update data @@ -548,66 +549,68 @@ -
- - - - - {$_('chart.show_slope')} - - - - - - {$_('chart.show_surface')} - - - - - - - - {velocityUnits === 'speed' ? $_('chart.show_speed') : $_('chart.show_pace')} - - - - - - {$_('chart.show_heartrate')} - - - - - - {$_('chart.show_cadence')} - - - - - - {$_('chart.show_temperature')} - - - - - - {$_('chart.show_power')} - - - -
+ {#if showControls} +
+ + + + + {$_('chart.show_slope')} + + + + + + {$_('chart.show_surface')} + + + + + + + + {$velocityUnits === 'speed' ? $_('chart.show_speed') : $_('chart.show_pace')} + + + + + + {$_('chart.show_heartrate')} + + + + + + {$_('chart.show_cadence')} + + + + + + {$_('chart.show_temperature')} + + + + + + {$_('chart.show_power')} + + + +
+ {/if} diff --git a/website/src/lib/components/EmbeddingPlayground.svelte b/website/src/lib/components/EmbeddingPlayground.svelte new file mode 100644 index 00000000..0d577d19 --- /dev/null +++ b/website/src/lib/components/EmbeddingPlayground.svelte @@ -0,0 +1,209 @@ + + + + + + + Card Title + Card Description + + +
+ + + + { + if (selected?.value) { + options.basemap = selected?.value; + } + }} + > + + + + + {#each Object.keys(basemaps) as basemap} + {$_(`layers.label.${basemap}`)} + {/each} + + +
+ + +
+ {#if options.elevation.show} +
+ +
+ + {$_('embedding.fill_by')} + + { + if (selected?.value) { + if (selected?.value === 'none') { + options.elevation.fill = undefined; + } else { + options.elevation.fill = selected?.value; + } + } + }} + > + + + + + {$_('quantities.slope')} + {$_('quantities.surface')} + {$_('embedding.none')} + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ {/if} +
+ + + +
+
+
+
diff --git a/website/src/lib/components/Export.svelte b/website/src/lib/components/Export.svelte index c9e2356c..0070e0df 100644 --- a/website/src/lib/components/Export.svelte +++ b/website/src/lib/components/Export.svelte @@ -9,7 +9,7 @@ exportState } from '$lib/stores'; import { fileObservers } from '$lib/db'; - import { Cloud, Download } from 'lucide-svelte'; + import { Download } from 'lucide-svelte'; import { _ } from 'svelte-i18n'; import { selection } from './file-list/Selection'; diff --git a/website/src/lib/components/GPXStatistics.svelte b/website/src/lib/components/GPXStatistics.svelte index fa59c1d9..cfb6c480 100644 --- a/website/src/lib/components/GPXStatistics.svelte +++ b/website/src/lib/components/GPXStatistics.svelte @@ -8,13 +8,15 @@ import { _ } from 'svelte-i18n'; import type { GPXStatistics } from 'gpx'; import type { Writable } from 'svelte/store'; + import { settings } from '$lib/db'; export let gpxStatistics: Writable; export let slicedGPXStatistics: Writable<[GPXStatistics, number, number] | undefined>; - export let velocityUnits: 'speed' | 'pace'; export let orientation: 'horizontal' | 'vertical'; export let panelSize: number; + const { velocityUnits } = settings; + let statistics: GPXStatistics; $: if ($slicedGPXStatistics !== undefined) { @@ -26,8 +28,8 @@ {velocityUnits === 'speed' ? $_('quantities.speed') : $_('quantities.pace')} ({$_( + >{$velocityUnits === 'speed' ? $_('quantities.speed') : $_('quantities.pace')} ({$_( 'quantities.moving' )} / {$_('quantities.total')}) diff --git a/website/src/lib/components/Head.svelte b/website/src/lib/components/Head.svelte index 16485b64..7dbbdd8f 100644 --- a/website/src/lib/components/Head.svelte +++ b/website/src/lib/components/Head.svelte @@ -4,7 +4,7 @@ import { languages } from '$lib/languages'; import { _, isLoading } from 'svelte-i18n'; - $: location = $page.route.id?.split('/').slice(1).at(1) ?? 'home'; + $: location = $page.route.id?.split('/')[2] ?? 'home'; diff --git a/website/src/lib/components/Map.svelte b/website/src/lib/components/Map.svelte index cbad5bba..0140a941 100644 --- a/website/src/lib/components/Map.svelte +++ b/website/src/lib/components/Map.svelte @@ -15,6 +15,8 @@ mapboxgl.accessToken = mapboxAccessToken; + export let geocoder = true; + let fitBoundsOptions: mapboxgl.FitBoundsOptions = { maxZoom: 15, linear: true, @@ -51,15 +53,17 @@ newMap.addControl(new mapboxgl.NavigationControl()); - newMap.addControl( - new MapboxGeocoder({ - accessToken: mapboxgl.accessToken, - mapboxgl: mapboxgl, - collapsed: true, - flyTo: fitBoundsOptions, - language: get(locale) - }) - ); + if (geocoder) { + newMap.addControl( + new MapboxGeocoder({ + accessToken: mapboxgl.accessToken, + mapboxgl: mapboxgl, + collapsed: true, + flyTo: fitBoundsOptions, + language: get(locale) + }) + ); + } newMap.addControl( new mapboxgl.GeolocateControl({ diff --git a/website/src/lib/components/Menu.svelte b/website/src/lib/components/Menu.svelte index 9cc79ebe..836d33c3 100644 --- a/website/src/lib/components/Menu.svelte +++ b/website/src/lib/components/Menu.svelte @@ -124,7 +124,7 @@
- + @@ -416,7 +416,7 @@
diff --git a/website/src/lib/components/docs/DocsLoader.svelte b/website/src/lib/components/docs/DocsLoader.svelte index 9075539d..3b5f77e2 100644 --- a/website/src/lib/components/docs/DocsLoader.svelte +++ b/website/src/lib/components/docs/DocsLoader.svelte @@ -80,6 +80,11 @@ @apply pl-4; } + :global(.markdown ol) { + @apply list-decimal; + @apply pl-4; + } + :global(.markdown li) { @apply mt-1; @apply first:mt-0; diff --git a/website/src/lib/components/docs/docs.ts b/website/src/lib/components/docs/docs.ts index a88965e7..770c2d02 100644 --- a/website/src/lib/components/docs/docs.ts +++ b/website/src/lib/components/docs/docs.ts @@ -8,6 +8,7 @@ export const guides: Record = { toolbar: ['routing', 'poi', 'scissors', 'time', 'merge', 'extract', 'minify', 'clean'], 'map-controls': [], 'gpx': [], + 'integration': [], }; export const guideIcons: Record> = { @@ -29,6 +30,7 @@ export const guideIcons: Record> = { "clean": SquareDashedMousePointer, "map-controls": "🗺", "gpx": "💾", + "integration": "{ 👩‍💻 }", }; export function getPreviousGuide(currentGuide: string): string | undefined { @@ -40,7 +42,7 @@ export function getPreviousGuide(currentGuide: string): string | undefined { if (index === 0) { return undefined; } - let previousGuide = keys.at(index - 1); + let previousGuide = keys[index - 1]; if (previousGuide === undefined) { return undefined; } else if (guides[previousGuide].length === 0) { @@ -65,7 +67,7 @@ export function getNextGuide(currentGuide: string): string | undefined { if (guides[currentGuide].length === 0) { let keys = Object.keys(guides); let index = keys.indexOf(currentGuide); - return keys.at(index + 1); + return keys[index + 1]; } else { return `${currentGuide}/${guides[currentGuide][0]}`; } @@ -76,7 +78,7 @@ export function getNextGuide(currentGuide: string): string | undefined { } else { let keys = Object.keys(guides); let index = keys.indexOf(subguides[0]); - return keys.at(index + 1); + return keys[index + 1]; } } } \ No newline at end of file diff --git a/website/src/lib/components/file-list/FileListNodeLabel.svelte b/website/src/lib/components/file-list/FileListNodeLabel.svelte index 1b4af97b..af650a8a 100644 --- a/website/src/lib/components/file-list/FileListNodeLabel.svelte +++ b/website/src/lib/components/file-list/FileListNodeLabel.svelte @@ -2,7 +2,7 @@ import { Button } from '$lib/components/ui/button'; import * as ContextMenu from '$lib/components/ui/context-menu'; import Shortcut from '$lib/components/Shortcut.svelte'; - import { dbUtils, getFile, settings } from '$lib/db'; + import { dbUtils, getFile } from '$lib/db'; import { Copy, Info, @@ -38,7 +38,7 @@ } from './Selection'; import { getContext } from 'svelte'; import { get } from 'svelte/store'; - import { allHidden, editMetadata, editStyle, gpxLayers, map } from '$lib/stores'; + import { allHidden, editMetadata, editStyle, embedding, gpxLayers, map } from '$lib/stores'; import { GPXTreeElement, Track, @@ -57,8 +57,6 @@ let orientation = getContext<'vertical' | 'horizontal'>('orientation'); - const { verticalFileView } = settings; - $: singleSelection = $selection.size === 1; let nodeColors: string[] = []; @@ -131,10 +129,12 @@ {/if} {#if item.level === ListLevel.FILE || item.level === ListLevel.TRACK}
`${c} ${Math.floor((100 * i) / nodeColors.length)}% ${Math.floor((100 * (i + 1)) / nodeColors.length)}%` @@ -147,6 +147,11 @@ ? 'text-muted-foreground' : ''}" on:contextmenu={(e) => { + if ($embedding) { + e.preventDefault(); + e.stopPropagation(); + return; + } if (e.ctrlKey) { // Add to selection instead of opening context menu e.preventDefault(); @@ -181,13 +186,13 @@ {:else if item.level === ListLevel.WAYPOINT} {/if} - + {label} {#if hidden} - {#if $verticalFileView} + {#if orientation === 'vertical'} {#if item instanceof ListFileItem} {/if} - {#if $verticalFileView} + {#if orientation === 'vertical'} {$_('menu.duplicate')} - {#if $verticalFileView} + {#if orientation === 'vertical'} {$_('menu.copy')} diff --git a/website/src/lib/db.ts b/website/src/lib/db.ts index 7141368b..53a24f0a 100644 --- a/website/src/lib/db.ts +++ b/website/src/lib/db.ts @@ -280,47 +280,49 @@ function commitFileStateChange(newFileState: ReadonlyMap, patch }); } -export const fileObservers: Writable & { destroy: () => void }>> = writable(new Map()); +export const fileObservers: Writable & { destroy?: () => void }>> = writable(new Map()); const fileState: Map = new Map(); // Used to generate patches // Observe the file ids in the database, and maintain a map of file observers for the corresponding files -liveQuery(() => db.fileids.toArray()).subscribe(dbFileIds => { - // Find new files to observe - let newFiles = dbFileIds.filter(id => !get(fileObservers).has(id)).sort((a, b) => parseInt(a.split('-')[1]) - parseInt(b.split('-')[1])); - // Find deleted files to stop observing - let deletedFiles = Array.from(get(fileObservers).keys()).filter(id => !dbFileIds.find(fileId => fileId === id)); +export function observeFilesFromDatabase() { + liveQuery(() => db.fileids.toArray()).subscribe(dbFileIds => { + // Find new files to observe + let newFiles = dbFileIds.filter(id => !get(fileObservers).has(id)).sort((a, b) => parseInt(a.split('-')[1]) - parseInt(b.split('-')[1])); + // Find deleted files to stop observing + let deletedFiles = Array.from(get(fileObservers).keys()).filter(id => !dbFileIds.find(fileId => fileId === id)); - // Update the store - if (newFiles.length > 0 || deletedFiles.length > 0) { - fileObservers.update($files => { - if (newFiles.length > 0) { // Reset the target map bounds when new files are added - initTargetMapBounds($files.size === 0); - } - newFiles.forEach(id => { - $files.set(id, dexieGPXFileStore(id)); - }); - deletedFiles.forEach(id => { - $files.get(id)?.destroy(); - $files.delete(id); - }); - return $files; - }); - settings.fileOrder.update((order) => { - newFiles.forEach((fileId) => { - if (!order.includes(fileId)) { - order.push(fileId); + // Update the store + if (newFiles.length > 0 || deletedFiles.length > 0) { + fileObservers.update($files => { + if (newFiles.length > 0) { // Reset the target map bounds when new files are added + initTargetMapBounds($files.size === 0); } + newFiles.forEach(id => { + $files.set(id, dexieGPXFileStore(id)); + }); + deletedFiles.forEach(id => { + $files.get(id)?.destroy?.(); + $files.delete(id); + }); + return $files; }); - deletedFiles.forEach((fileId) => { - let index = order.indexOf(fileId); - if (index !== -1) { - order.splice(index, 1); - } + settings.fileOrder.update((order) => { + newFiles.forEach((fileId) => { + if (!order.includes(fileId)) { + order.push(fileId); + } + }); + deletedFiles.forEach((fileId) => { + let index = order.indexOf(fileId); + if (index !== -1) { + order.splice(index, 1); + } + }); + return order; }); - return order; - }); - } -}); + } + }); +} export function getFile(fileId: string): GPXFile | undefined { let fileStore = get(fileObservers).get(fileId); diff --git a/website/src/lib/docs/en/integration.mdx b/website/src/lib/docs/en/integration.mdx new file mode 100644 index 00000000..e3ec732a --- /dev/null +++ b/website/src/lib/docs/en/integration.mdx @@ -0,0 +1,11 @@ +--- +title: Integration +--- + + + +# { title } + + diff --git a/website/src/lib/stores.ts b/website/src/lib/stores.ts index e8d3da8a..b053fd26 100644 --- a/website/src/lib/stores.ts +++ b/website/src/lib/stores.ts @@ -14,6 +14,7 @@ import { SplitType } from '$lib/components/toolbar/tools/Scissors.svelte'; const { fileOrder } = settings; export const map = writable(null); +export const embedding = writable(false); export const selectFiles = writable<{ [key: string]: (fileId?: string) => void }>({}); export const gpxStatistics: Writable = writable(new GPXStatistics()); @@ -178,7 +179,7 @@ export function triggerFileInput() { input.click(); } -export async function loadFiles(list: FileList) { +export async function loadFiles(list: FileList | File[]) { let files = []; for (let i = 0; i < list.length; i++) { let file = await loadFile(list[i]); diff --git a/website/src/locales/en.json b/website/src/locales/en.json index 36d45271..2594358d 100644 --- a/website/src/locales/en.json +++ b/website/src/locales/en.json @@ -2,6 +2,7 @@ "metadata": { "home_title": "home", "app_title": "the online GPX file editor", + "embed_title": "the online GPX file editor", "help_title": "help", "description": "View, edit and create GPX files online with advanced route planning capabilities and file processing tools, beautiful maps and detailed data visualizations." }, @@ -70,7 +71,8 @@ "width": "Width" }, "hide": "Hide", - "unhide": "Unhide" + "unhide": "Unhide", + "open_in": "Open in" }, "toolbar": { "routing": { @@ -368,5 +370,20 @@ "data_visualization_description": "An interactive elevation profile with detailed statistics to analyze recorded activities and future objectives.", "identity": "Free, ad-free and open-source", "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": { + "file_urls": "File URLs (separated by commas)", + "basemap": "Basemap", + "height": "Height", + "fill_by": "Fill by", + "none": "None", + "show_controls": "Show controls", + "manual_camera": "Manual camera", + "manual_camera_description": "You can move the map below to adjust the camera position.", + "zoom": "Zoom", + "latitude": "Latitude", + "longitude": "Longitude", + "pitch": "Pitch", + "bearing": "Bearing" } } \ No newline at end of file diff --git a/website/src/routes/[...language]/+layout.svelte b/website/src/routes/[...language]/+layout.svelte index fcce3dbc..9749fc1f 100644 --- a/website/src/routes/[...language]/+layout.svelte +++ b/website/src/routes/[...language]/+layout.svelte @@ -12,14 +12,14 @@ locale.set($page.params.language.replace('/', '')); } - const appRoute = '/[...language]/app'; + const appRoutes = ['/[...language]/app', '/[...language]/embed']; {#if !$isLoading} - {#if $page.route.id === appRoute} + {#if $page.route.id !== null && appRoutes.includes($page.route.id)} {:else}
- - @@ -161,9 +153,6 @@ additionalDatasets={$additionalDatasets} elevationFill={$elevationFill} panelSize={200} - distanceUnits={$distanceUnits} - velocityUnits={$velocityUnits} - temperatureUnits={$temperatureUnits} />
@@ -173,7 +162,6 @@ {slicedGPXStatistics} panelSize={192} orientation={'horizontal'} - velocityUnits={$velocityUnits} />
diff --git a/website/src/routes/[...language]/app/+page.svelte b/website/src/routes/[...language]/app/+page.svelte index 424a6359..482895f3 100644 --- a/website/src/routes/[...language]/app/+page.svelte +++ b/website/src/routes/[...language]/app/+page.svelte @@ -1,5 +1,108 @@ - +
+
+
+ +
+ +
+ + + + + + {#if !$verticalFileView} +
+ +
+ {/if} +
+ {#if $elevationProfile} + + {/if} +
+ + {#if $elevationProfile} + + {/if} +
+
+ {#if $verticalFileView} + + + {/if} +
+ + diff --git a/website/src/routes/[...language]/embed/+page.svelte b/website/src/routes/[...language]/embed/+page.svelte new file mode 100644 index 00000000..36d70ffb --- /dev/null +++ b/website/src/routes/[...language]/embed/+page.svelte @@ -0,0 +1,231 @@ + + +
+
+ + + + + {#if $fileObservers.size > 1} +
+ +
+ {/if} +
+
+ + {#if elevationProfile} + + {/if} +
+
diff --git a/website/src/routes/[...language]/help/+layout.svelte b/website/src/routes/[...language]/help/+layout.svelte index 05bb58f6..e9963c85 100644 --- a/website/src/routes/[...language]/help/+layout.svelte +++ b/website/src/routes/[...language]/help/+layout.svelte @@ -8,12 +8,12 @@
-