diff --git a/website/src/lib/components/layer-control/CustomLayers.svelte b/website/src/lib/components/layer-control/CustomLayers.svelte index bbb27464..6d5aa8d9 100644 --- a/website/src/lib/components/layer-control/CustomLayers.svelte +++ b/website/src/lib/components/layer-control/CustomLayers.svelte @@ -3,12 +3,26 @@ import { Input } from '$lib/components/ui/input'; import { Label } from '$lib/components/ui/label'; import { Button } from '$lib/components/ui/button'; + import { Separator } from '$lib/components/ui/separator'; import * as RadioGroup from '$lib/components/ui/radio-group'; - import { CirclePlus, CircleX, Minus, Pencil, Plus, Save, Trash2 } from 'lucide-svelte'; + import { + CirclePlus, + CircleX, + Minus, + Pencil, + Plus, + Save, + Trash2, + Move, + Map, + Layers2 + } from 'lucide-svelte'; import { _ } from 'svelte-i18n'; import { settings } from '$lib/db'; import { defaultBasemap, extendBasemap, type CustomLayer } from '$lib/assets/layers'; import { map } from '$lib/stores'; + import { onDestroy, onMount } from 'svelte'; + import Sortable from 'sortablejs/Sortable'; const { customLayers, @@ -17,7 +31,9 @@ currentBasemap, previousBasemap, currentOverlays, - previousOverlays + previousOverlays, + customBasemapOrder, + customOverlayOrder } = settings; let name: string = ''; @@ -26,6 +42,52 @@ let layerType: 'basemap' | 'overlay' = 'basemap'; let resourceType: 'raster' | 'vector' = 'raster'; + let basemapContainer: HTMLElement; + let overlayContainer: HTMLElement; + + let basemapSortable: Sortable; + let overlaySortable: Sortable; + + onMount(() => { + if ($customBasemapOrder.length === 0) { + $customBasemapOrder = Object.keys($customLayers).filter( + (id) => $customLayers[id].layerType === 'basemap' + ); + } + if ($customOverlayOrder.length === 0) { + $customOverlayOrder = Object.keys($customLayers).filter( + (id) => $customLayers[id].layerType === 'overlay' + ); + } + + basemapSortable = Sortable.create(basemapContainer, { + onSort: (e) => { + $customBasemapOrder = basemapSortable.toArray(); + $selectedBasemapTree.basemaps['custom'] = $customBasemapOrder.reduce((acc, id) => { + acc[id] = true; + return acc; + }, {}); + } + }); + overlaySortable = Sortable.create(overlayContainer, { + onSort: (e) => { + $customOverlayOrder = overlaySortable.toArray(); + $selectedOverlayTree.overlays['custom'] = $customOverlayOrder.reduce((acc, id) => { + acc[id] = true; + return acc; + }, {}); + } + }); + + basemapSortable.sort($customBasemapOrder); + overlaySortable.sort($customOverlayOrder); + }); + + onDestroy(() => { + basemapSortable.destroy(); + overlaySortable.destroy(); + }); + $: if (tileUrls[0].length > 0) { if ( tileUrls[0].includes('.json') || @@ -112,6 +174,10 @@ }); $currentBasemap = layerId; + + if (!$customBasemapOrder.includes(layerId)) { + $customBasemapOrder = [...$customBasemapOrder, layerId]; + } } else { selectedOverlayTree.update(($tree) => { if (!$tree.overlays.hasOwnProperty('custom')) { @@ -129,7 +195,14 @@ $map.removeSource(layerId); } + if (!$currentOverlays.overlays.hasOwnProperty('custom')) { + $currentOverlays.overlays['custom'] = {}; + } $currentOverlays.overlays['custom'][layerId] = true; + + if (!$customOverlayOrder.includes(layerId)) { + $customOverlayOrder = [...$customOverlayOrder, layerId]; + } } } @@ -157,6 +230,7 @@ if (Object.keys($selectedBasemapTree.basemaps['custom']).length === 0) { $selectedBasemapTree.basemaps = tryDeleteLayer($selectedBasemapTree.basemaps, 'custom'); } + $customBasemapOrder = $customBasemapOrder.filter((id) => id !== layerId); } else { $currentOverlays.overlays['custom'][layerId] = false; if ($previousOverlays.overlays['custom']) { @@ -173,6 +247,7 @@ if (Object.keys($selectedOverlayTree.overlays['custom']).length === 0) { $selectedOverlayTree.overlays = tryDeleteLayer($selectedOverlayTree.overlays, 'custom'); } + $customOverlayOrder = $customOverlayOrder.filter((id) => id !== layerId); if ($map) { if ($map.getLayer(layerId)) { @@ -208,96 +283,131 @@ $: selectedLayerId, setDataFromSelectedLayer(); -{#if Object.keys($customLayers).length > 0} -
- {#each Object.entries($customLayers) as [id, layer] (id)} -
- {layer.name} - - +
+
+ {#if $customBasemapOrder.length > 0} +
+ + {$_('layers.label.basemaps')} +
+ +
- {/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} + {/if} +
+ {#each $customBasemapOrder as id (id)} +
+ + {$customLayers[id].name} + +
{/each} - {#if resourceType === 'raster'} - - - {/if} - - -
- - +
+ {#if $customOverlayOrder.length > 0} +
+ + {$_('layers.label.overlays')} +
+
-
- - -
- - {#if selectedLayerId} -
-
+ {/if} +
+ {#each $customOverlayOrder as id (id)} +
+ + {$customLayers[id].name} + -
- {:else} - - {/if} -
-
-
+ {/each} +
+
+ + + + + {#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/db.ts b/website/src/lib/db.ts index dd1837fc..97a7d26b 100644 --- a/website/src/lib/db.ts +++ b/website/src/lib/db.ts @@ -93,6 +93,8 @@ export const settings = { selectedOverpassTree: dexieSettingStore('selectedOverpassTree', defaultOverpassTree), opacities: dexieSettingStore('opacities', defaultOpacities), customLayers: dexieSettingStore>('customLayers', {}), + customBasemapOrder: dexieSettingStore('customBasemapOrder', []), + customOverlayOrder: dexieSettingStore('customOverlayOrder', []), directionMarkers: dexieSettingStore('directionMarkers', false), distanceMarkers: dexieSettingStore('distanceMarkers', false), stravaHeatmapColor: dexieSettingStore('stravaHeatmapColor', 'bluered'),