diff --git a/website/src/lib/assets/layers.ts b/website/src/lib/assets/layers.ts index 98780282..ffc6f2bc 100644 --- a/website/src/lib/assets/layers.ts +++ b/website/src/lib/assets/layers.ts @@ -266,12 +266,15 @@ export const basemaps: { [key: string]: string | Style; } = { }, }; -Object.values(basemaps).forEach((basemap) => { +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}`; } -}); + return basemap; +} + +Object.values(basemaps).forEach(extendBasemap); export const font: { [key: string]: string; } = { swisstopo: 'Frutiger Neue Condensed Regular', @@ -678,6 +681,16 @@ export const defaultOverlayTree: LayerTreeType = { } } +export type CustomLayer = { + id: string, + name: string, + tileUrls: string[], + maxZoom: number, + layerType: 'basemap' | 'overlay', + resourceType: 'raster' | 'vector', + value: string | {}, +}; + export const stravaHeatmapServers = ['https://heatmap-external-a.strava.com/tiles-auth', 'https://heatmap-external-b.strava.com/tiles-auth', 'https://heatmap-external-c.strava.com/tiles-auth']; export const stravaHeatmapActivityIds: { [key: string]: string } = { stravaHeatmapRun: 'sport_Run', diff --git a/website/src/lib/components/Menu.svelte b/website/src/lib/components/Menu.svelte index 2fe77239..3b19207a 100644 --- a/website/src/lib/components/Menu.svelte +++ b/website/src/lib/components/Menu.svelte @@ -395,8 +395,10 @@ cutSelection(); e.preventDefault(); } else if (e.key === 'v' && (e.metaKey || e.ctrlKey)) { - pasteSelection(); - e.preventDefault(); + if (e.target.tagName !== 'INPUT' && e.target.tagName !== 'TEXTAREA') { + pasteSelection(); + e.preventDefault(); + } } else if ((e.key === 's' || e.key == 'S') && (e.metaKey || e.ctrlKey)) { if (e.shiftKey) { exportAllFiles(); diff --git a/website/src/lib/components/layer-control/CustomLayers.svelte b/website/src/lib/components/layer-control/CustomLayers.svelte new file mode 100644 index 00000000..846f3194 --- /dev/null +++ b/website/src/lib/components/layer-control/CustomLayers.svelte @@ -0,0 +1,264 @@ + + +{#if Object.keys($customLayers).length > 0} +
+ {#each Object.entries($customLayers) as [id, layer] (id)} +
+ {layer.name} + + +
+ {/each} +
+{/if} + + + + + {#if selectedLayerId} + {$_('layers.custom_layers.edit')} + {:else} + {$_('layers.custom_layers.new')} + {/if} + + + +
+ + + + {#each tileUrls as url, i} +
+ + {#if tileUrls.length > 1} + + {/if} + {#if i === tileUrls.length - 1} + + {/if} +
+ {/each} + {#if resourceType === 'raster'} + + + {/if} + + +
+ + +
+
+ + +
+
+ {#if selectedLayerId} +
+ + +
+ {:else} + + {/if} +
+
+
diff --git a/website/src/lib/components/layer-control/LayerControl.svelte b/website/src/lib/components/layer-control/LayerControl.svelte index 3d041eac..e9210331 100644 --- a/website/src/lib/components/layer-control/LayerControl.svelte +++ b/website/src/lib/components/layer-control/LayerControl.svelte @@ -18,12 +18,16 @@ previousBasemap, currentOverlays, selectedBasemapTree, - selectedOverlayTree + selectedOverlayTree, + customLayers } = settings; $: if ($map) { // Set style depending on the current basemap - $map.setStyle(basemaps[$currentBasemap], { + let basemap = basemaps.hasOwnProperty($currentBasemap) + ? basemaps[$currentBasemap] + : $customLayers[$currentBasemap].value; + $map.setStyle(basemap, { diff: false }); } @@ -65,17 +69,18 @@ return () => { if ($map) { try { + let overlay = $customLayers.hasOwnProperty(id) ? $customLayers[id].value : overlays[id]; if (!$map.getSource(id)) { - $map.addSource(id, overlays[id]); + $map.addSource(id, overlay); } $map.addLayer( { id, - type: overlays[id].type === 'raster' ? 'raster' : 'line', + type: overlay.type === 'raster' ? 'raster' : 'line', source: id, paint: { ...(id in opacities - ? overlays[id].type === 'raster' + ? overlay.type === 'raster' ? { 'raster-opacity': opacities[id] } : { 'line-opacity': opacities[id] } : {}) diff --git a/website/src/lib/components/layer-control/LayerControlSettings.svelte b/website/src/lib/components/layer-control/LayerControlSettings.svelte index e15f338a..417886b9 100644 --- a/website/src/lib/components/layer-control/LayerControlSettings.svelte +++ b/website/src/lib/components/layer-control/LayerControlSettings.svelte @@ -15,6 +15,7 @@ import { writable, get } from 'svelte/store'; import { map, setStravaHeatmapURLs } from '$lib/stores'; import { browser } from '$app/environment'; + import CustomLayers from './CustomLayers.svelte'; const { selectedBasemapTree, selectedOverlayTree, stravaHeatmapColor, currentOverlays } = settings; @@ -111,9 +112,11 @@ - {$_('layers.custom_layers')} + {$_('layers.custom_layers.title')} - + + + diff --git a/website/src/lib/components/layer-control/LayerTreeNode.svelte b/website/src/lib/components/layer-control/LayerTreeNode.svelte index a980a0fa..5f3d84db 100644 --- a/website/src/lib/components/layer-control/LayerTreeNode.svelte +++ b/website/src/lib/components/layer-control/LayerTreeNode.svelte @@ -7,6 +7,7 @@ import { anySelectedLayer } from './utils'; import { _ } from 'svelte-i18n'; + import { settings } from '$lib/db'; export let name: string; export let node: LayerTreeType; @@ -15,15 +16,19 @@ export let checked: LayerTreeType; - Object.keys(node).forEach((id) => { - if (!checked.hasOwnProperty(id)) { - if (typeof node[id] == 'boolean') { - checked[id] = false; - } else { - checked[id] = {}; + const { customLayers } = settings; + + $: if (checked !== undefined) { + Object.keys(node).forEach((id) => { + if (!checked.hasOwnProperty(id)) { + if (typeof node[id] == 'boolean') { + checked[id] = false; + } else { + checked[id] = {}; + } } - } - }); + }); + }
@@ -42,9 +47,13 @@ {:else} {/if} - +
{/if} {:else if anySelectedLayer(node[id])} diff --git a/website/src/lib/db.ts b/website/src/lib/db.ts index c2ea20d5..e80b0cb1 100644 --- a/website/src/lib/db.ts +++ b/website/src/lib/db.ts @@ -4,7 +4,7 @@ import { enableMapSet, enablePatches, applyPatches, type Patch, type WritableDra import { writable, get, derived, type Readable, type Writable } from 'svelte/store'; import { gpxStatistics, initTargetMapBounds, splitAs, updateTargetMapBounds } from './stores'; import { mode } from 'mode-watcher'; -import { defaultBasemap, defaultBasemapTree, defaultOverlayTree, defaultOverlays } from './assets/layers'; +import { defaultBasemap, defaultBasemapTree, defaultOverlayTree, defaultOverlays, type CustomLayer } from './assets/layers'; import { applyToOrderedItemsFromFile, applyToOrderedSelectedItemsFromFile, selection } from '$lib/components/file-list/Selection'; import { ListFileItem, ListItem, ListTrackItem, ListLevel, ListTrackSegmentItem, ListWaypointItem, ListRootItem } from '$lib/components/file-list/FileList'; import { updateAnchorPoints } from '$lib/components/toolbar/tools/routing/Simplify'; @@ -48,13 +48,13 @@ function dexieSettingStore(setting: string, initial: T): Writable { return { subscribe: store.subscribe, set: (value: any) => { - if (value !== get(store)) { + if (typeof value === 'object' || value !== get(store)) { db.settings.put(value, setting); } }, update: (callback: (value: any) => any) => { let newValue = callback(get(store)); - if (newValue !== get(store)) { + if (typeof newValue === 'object' || newValue !== get(store)) { db.settings.put(newValue, setting); } } @@ -104,6 +104,7 @@ export const settings = { currentOverlays: dexieUninitializedSettingStore('currentOverlays', defaultOverlays), previousOverlays: dexieSettingStore('previousOverlays', defaultOverlays), selectedOverlayTree: dexieSettingStore('selectedOverlayTree', defaultOverlayTree), + customLayers: dexieSettingStore>('customLayers', {}), directionMarkers: dexieSettingStore('directionMarkers', false), distanceMarkers: dexieSettingStore('distanceMarkers', false), stravaHeatmapColor: dexieSettingStore('stravaHeatmapColor', 'bluered'), diff --git a/website/src/locales/en.json b/website/src/locales/en.json index 8511c616..bb39b0d4 100644 --- a/website/src/locales/en.json +++ b/website/src/locales/en.json @@ -198,12 +198,24 @@ "settings": "Layer settings", "settings_help": "Select the map layers you want to show in the interface, add custom ones, and adjust their settings.", "selection": "Layer selection", - "custom_layers": "Custom layers", + "custom_layers": { + "title": "Custom layers", + "new": "New custom layer", + "edit": "Edit custom layer", + "urls": "URL(s)", + "max_zoom": "Max zoom", + "layer_type": "Layer type", + "basemap": "Basemap", + "overlay": "Overlay", + "create": "Create layer", + "update": "Update layer" + }, "heatmap": "Strava Heatmap", "pois": "Points of interest", "label": { "basemaps": "Basemaps", "overlays": "Overlays", + "custom": "Custom", "world": "World", "countries": "Countries", "belgium": "Belgium",