migrate custom layers sortable list from sortablejs to svelte-dnd-action

This commit is contained in:
vcoppe
2025-10-25 18:34:24 +02:00
parent 1b035bcde3
commit 2e828dfde3
3 changed files with 84 additions and 46 deletions

View File

@@ -62,6 +62,7 @@
"prettier-plugin-svelte": "^3.4.0", "prettier-plugin-svelte": "^3.4.0",
"svelte": "^5.33.18", "svelte": "^5.33.18",
"svelte-check": "^4.0.0", "svelte-check": "^4.0.0",
"svelte-dnd-action": "^0.9.65",
"svelte-sonner": "^1.0.5", "svelte-sonner": "^1.0.5",
"tailwind-variants": "^3.1.1", "tailwind-variants": "^3.1.1",
"tailwindcss": "^4.1.8", "tailwindcss": "^4.1.8",
@@ -8242,6 +8243,16 @@
"url": "https://paulmillr.com/funding/" "url": "https://paulmillr.com/funding/"
} }
}, },
"node_modules/svelte-dnd-action": {
"version": "0.9.65",
"resolved": "https://registry.npmjs.org/svelte-dnd-action/-/svelte-dnd-action-0.9.65.tgz",
"integrity": "sha512-GKFtrAtYAjcm27aMELoXOhkLtKA1AEoj2njjCReCer6jh1hnRtTHdEO4Kjfpayz+ZAvE0MMwIvLISW3tsiO9Qg==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"svelte": ">=3.23.0 || ^5.0.0-next.0"
}
},
"node_modules/svelte-eslint-parser": { "node_modules/svelte-eslint-parser": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.2.0.tgz", "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.2.0.tgz",

View File

@@ -46,6 +46,7 @@
"prettier-plugin-svelte": "^3.4.0", "prettier-plugin-svelte": "^3.4.0",
"svelte": "^5.33.18", "svelte": "^5.33.18",
"svelte-check": "^4.0.0", "svelte-check": "^4.0.0",
"svelte-dnd-action": "^0.9.65",
"svelte-sonner": "^1.0.5", "svelte-sonner": "^1.0.5",
"tailwind-variants": "^3.1.1", "tailwind-variants": "^3.1.1",
"tailwindcss": "^4.1.8", "tailwindcss": "^4.1.8",

View File

@@ -19,11 +19,11 @@
} from '@lucide/svelte'; } from '@lucide/svelte';
import { i18n } from '$lib/i18n.svelte'; import { i18n } from '$lib/i18n.svelte';
import { defaultBasemap, type CustomLayer } from '$lib/assets/layers'; import { defaultBasemap, type CustomLayer } from '$lib/assets/layers';
import { onDestroy, onMount } from 'svelte'; import { onMount } from 'svelte';
import Sortable from 'sortablejs/Sortable';
import { customBasemapUpdate } from './utils'; import { customBasemapUpdate } from './utils';
import { settings } from '$lib/logic/settings'; import { settings } from '$lib/logic/settings';
import { map } from '$lib/components/map/map'; import { map } from '$lib/components/map/map';
import { dndzone } from 'svelte-dnd-action';
const { const {
customLayers, customLayers,
@@ -55,12 +55,6 @@
let selectedLayerId: string | undefined = $state(undefined); let selectedLayerId: string | undefined = $state(undefined);
let basemapContainer: HTMLElement;
let overlayContainer: HTMLElement;
let basemapSortable: Sortable;
let overlaySortable: Sortable;
onMount(() => { onMount(() => {
if ($customBasemapOrder.length === 0) { if ($customBasemapOrder.length === 0) {
$customBasemapOrder = Object.keys($customLayers).filter( $customBasemapOrder = Object.keys($customLayers).filter(
@@ -72,34 +66,26 @@
(id) => $customLayers[id].layerType === 'overlay' (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(() => { let customBasemapItems: {
basemapSortable.destroy(); id: string;
overlaySortable.destroy(); name: string;
}); }[] = $derived(
$customBasemapOrder.map((id) => ({
id: id,
name: $customLayers[id].name,
}))
);
let customOverlayItems: {
id: string;
name: string;
}[] = $derived(
$customOverlayOrder.map((id) => ({
id: id,
name: $customLayers[id].name,
}))
);
$effect(() => { $effect(() => {
setDataFromSelectedLayer(selectedLayerId); setDataFromSelectedLayer(selectedLayerId);
@@ -306,17 +292,37 @@
</div> </div>
{/if} {/if}
<div <div
bind:this={basemapContainer}
class="ml-1.5 flex flex-col gap-1 {$customBasemapOrder.length > 0 ? 'mb-2' : ''}" class="ml-1.5 flex flex-col gap-1 {$customBasemapOrder.length > 0 ? 'mb-2' : ''}"
use:dndzone={{
items: customBasemapItems,
type: 'basemap',
dropTargetStyle: {},
transformDraggedElement: (element) => {
if (element) {
element.style.opacity = '0.5';
}
},
}}
onconsider={(e) => {
customBasemapItems = e.detail.items;
}}
onfinalize={(e) => {
customBasemapItems = e.detail.items;
$customBasemapOrder = customBasemapItems.map((item) => item.id);
$selectedBasemapTree.basemaps['custom'] = customBasemapItems.reduce((acc, item) => {
acc[item.id] = true;
return acc;
}, {});
}}
> >
{#each $customBasemapOrder as id (id)} {#each customBasemapItems as item (item.id)}
<div class="flex flex-row items-center gap-2" data-id={id}> <div class="flex flex-row items-center gap-2">
<Move size="12" /> <Move size="12" />
<span class="grow">{$customLayers[id].name}</span> <span class="grow">{item.name}</span>
<Button <Button
variant="outline" variant="outline"
size="icon-sm" size="icon-sm"
onclick={() => (selectedLayerId = id)} onclick={() => (selectedLayerId = item.id)}
class="p-1 h-7" class="p-1 h-7"
> >
<Pencil size="16" /> <Pencil size="16" />
@@ -324,7 +330,7 @@
<Button <Button
variant="outline" variant="outline"
size="icon-sm" size="icon-sm"
onclick={() => deleteLayer(id)} onclick={() => deleteLayer(item.id)}
class="p-1 h-7" class="p-1 h-7"
> >
<Trash2 size="16" /> <Trash2 size="16" />
@@ -342,17 +348,37 @@
</div> </div>
{/if} {/if}
<div <div
bind:this={overlayContainer}
class="ml-1.5 flex flex-col gap-1 {$customOverlayOrder.length > 0 ? 'mb-2' : ''}" class="ml-1.5 flex flex-col gap-1 {$customOverlayOrder.length > 0 ? 'mb-2' : ''}"
use:dndzone={{
items: customOverlayItems,
type: 'overlay',
dropTargetStyle: {},
transformDraggedElement: (element) => {
if (element) {
element.style.opacity = '0.5';
}
},
}}
onconsider={(e) => {
customOverlayItems = e.detail.items;
}}
onfinalize={(e) => {
customOverlayItems = e.detail.items;
$customOverlayOrder = customOverlayItems.map((item) => item.id);
$selectedOverlayTree.overlays['custom'] = customOverlayItems.reduce((acc, item) => {
acc[item.id] = true;
return acc;
}, {});
}}
> >
{#each $customOverlayOrder as id (id)} {#each customOverlayItems as item (item.id)}
<div class="flex flex-row items-center gap-2" data-id={id}> <div class="flex flex-row items-center gap-2">
<Move size="12" /> <Move size="12" />
<span class="grow">{$customLayers[id].name}</span> <span class="grow">{item.name}</span>
<Button <Button
variant="outline" variant="outline"
size="icon-sm" size="icon-sm"
onclick={() => (selectedLayerId = id)} onclick={() => (selectedLayerId = item.id)}
class="p-1 h-7" class="p-1 h-7"
> >
<Pencil size="16" /> <Pencil size="16" />
@@ -360,7 +386,7 @@
<Button <Button
variant="outline" variant="outline"
size="icon-sm" size="icon-sm"
onclick={() => deleteLayer(id)} onclick={() => deleteLayer(item.id)}
class="p-1 h-7" class="p-1 h-7"
> >
<Trash2 size="16" /> <Trash2 size="16" />