mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-11-04 21:41:07 +00:00
migrate custom layers sortable list from sortablejs to svelte-dnd-action
This commit is contained in:
11
website/package-lock.json
generated
11
website/package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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);
|
let customBasemapItems: {
|
||||||
overlaySortable.sort($customOverlayOrder);
|
id: string;
|
||||||
});
|
name: string;
|
||||||
|
}[] = $derived(
|
||||||
onDestroy(() => {
|
$customBasemapOrder.map((id) => ({
|
||||||
basemapSortable.destroy();
|
id: id,
|
||||||
overlaySortable.destroy();
|
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" />
|
||||||
|
|||||||
Reference in New Issue
Block a user