mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-09-02 08:42:31 +00:00
split custom basemaps and overlays, and allow reordering
This commit is contained in:
@@ -3,12 +3,26 @@
|
|||||||
import { Input } from '$lib/components/ui/input';
|
import { Input } from '$lib/components/ui/input';
|
||||||
import { Label } from '$lib/components/ui/label';
|
import { Label } from '$lib/components/ui/label';
|
||||||
import { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
|
import { Separator } from '$lib/components/ui/separator';
|
||||||
import * as RadioGroup from '$lib/components/ui/radio-group';
|
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 { _ } from 'svelte-i18n';
|
||||||
import { settings } from '$lib/db';
|
import { settings } from '$lib/db';
|
||||||
import { defaultBasemap, extendBasemap, type CustomLayer } from '$lib/assets/layers';
|
import { defaultBasemap, extendBasemap, type CustomLayer } from '$lib/assets/layers';
|
||||||
import { map } from '$lib/stores';
|
import { map } from '$lib/stores';
|
||||||
|
import { onDestroy, onMount } from 'svelte';
|
||||||
|
import Sortable from 'sortablejs/Sortable';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
customLayers,
|
customLayers,
|
||||||
@@ -17,7 +31,9 @@
|
|||||||
currentBasemap,
|
currentBasemap,
|
||||||
previousBasemap,
|
previousBasemap,
|
||||||
currentOverlays,
|
currentOverlays,
|
||||||
previousOverlays
|
previousOverlays,
|
||||||
|
customBasemapOrder,
|
||||||
|
customOverlayOrder
|
||||||
} = settings;
|
} = settings;
|
||||||
|
|
||||||
let name: string = '';
|
let name: string = '';
|
||||||
@@ -26,6 +42,52 @@
|
|||||||
let layerType: 'basemap' | 'overlay' = 'basemap';
|
let layerType: 'basemap' | 'overlay' = 'basemap';
|
||||||
let resourceType: 'raster' | 'vector' = 'raster';
|
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].length > 0) {
|
||||||
if (
|
if (
|
||||||
tileUrls[0].includes('.json') ||
|
tileUrls[0].includes('.json') ||
|
||||||
@@ -112,6 +174,10 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
$currentBasemap = layerId;
|
$currentBasemap = layerId;
|
||||||
|
|
||||||
|
if (!$customBasemapOrder.includes(layerId)) {
|
||||||
|
$customBasemapOrder = [...$customBasemapOrder, layerId];
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
selectedOverlayTree.update(($tree) => {
|
selectedOverlayTree.update(($tree) => {
|
||||||
if (!$tree.overlays.hasOwnProperty('custom')) {
|
if (!$tree.overlays.hasOwnProperty('custom')) {
|
||||||
@@ -129,7 +195,14 @@
|
|||||||
$map.removeSource(layerId);
|
$map.removeSource(layerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!$currentOverlays.overlays.hasOwnProperty('custom')) {
|
||||||
|
$currentOverlays.overlays['custom'] = {};
|
||||||
|
}
|
||||||
$currentOverlays.overlays['custom'][layerId] = true;
|
$currentOverlays.overlays['custom'][layerId] = true;
|
||||||
|
|
||||||
|
if (!$customOverlayOrder.includes(layerId)) {
|
||||||
|
$customOverlayOrder = [...$customOverlayOrder, layerId];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,6 +230,7 @@
|
|||||||
if (Object.keys($selectedBasemapTree.basemaps['custom']).length === 0) {
|
if (Object.keys($selectedBasemapTree.basemaps['custom']).length === 0) {
|
||||||
$selectedBasemapTree.basemaps = tryDeleteLayer($selectedBasemapTree.basemaps, 'custom');
|
$selectedBasemapTree.basemaps = tryDeleteLayer($selectedBasemapTree.basemaps, 'custom');
|
||||||
}
|
}
|
||||||
|
$customBasemapOrder = $customBasemapOrder.filter((id) => id !== layerId);
|
||||||
} else {
|
} else {
|
||||||
$currentOverlays.overlays['custom'][layerId] = false;
|
$currentOverlays.overlays['custom'][layerId] = false;
|
||||||
if ($previousOverlays.overlays['custom']) {
|
if ($previousOverlays.overlays['custom']) {
|
||||||
@@ -173,6 +247,7 @@
|
|||||||
if (Object.keys($selectedOverlayTree.overlays['custom']).length === 0) {
|
if (Object.keys($selectedOverlayTree.overlays['custom']).length === 0) {
|
||||||
$selectedOverlayTree.overlays = tryDeleteLayer($selectedOverlayTree.overlays, 'custom');
|
$selectedOverlayTree.overlays = tryDeleteLayer($selectedOverlayTree.overlays, 'custom');
|
||||||
}
|
}
|
||||||
|
$customOverlayOrder = $customOverlayOrder.filter((id) => id !== layerId);
|
||||||
|
|
||||||
if ($map) {
|
if ($map) {
|
||||||
if ($map.getLayer(layerId)) {
|
if ($map.getLayer(layerId)) {
|
||||||
@@ -208,21 +283,55 @@
|
|||||||
$: selectedLayerId, setDataFromSelectedLayer();
|
$: selectedLayerId, setDataFromSelectedLayer();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if Object.keys($customLayers).length > 0}
|
<div class="flex flex-col gap-3">
|
||||||
<div class="flex flex-col gap-1 mb-3">
|
<div class="flex flex-col gap-2">
|
||||||
{#each Object.entries($customLayers) as [id, layer] (id)}
|
{#if $customBasemapOrder.length > 0}
|
||||||
<div class="flex flex-row items-center gap-2">
|
<div class="flex flex-row items-center gap-1 font-semibold">
|
||||||
<span class="grow">{layer.name}</span>
|
<Map size="16" />
|
||||||
<Button variant="outline" on:click={() => (selectedLayerId = id)} class="p-1 h-8">
|
{$_('layers.label.basemaps')}
|
||||||
|
<div class="grow">
|
||||||
|
<Separator />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<div bind:this={basemapContainer} class="ml-1.5 flex flex-col gap-1">
|
||||||
|
{#each $customBasemapOrder as id (id)}
|
||||||
|
<div class="flex flex-row items-center gap-2" data-id={id}>
|
||||||
|
<Move size="12" />
|
||||||
|
<span class="grow">{$customLayers[id].name}</span>
|
||||||
|
<Button variant="outline" on:click={() => (selectedLayerId = id)} class="p-1 h-7">
|
||||||
<Pencil size="16" />
|
<Pencil size="16" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outline" on:click={() => deleteLayer(id)} class="p-1 h-8">
|
<Button variant="outline" on:click={() => deleteLayer(id)} class="p-1 h-7">
|
||||||
<Trash2 size="16" />
|
<Trash2 size="16" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
{#if $customOverlayOrder.length > 0}
|
||||||
|
<div class="flex flex-row items-center gap-1 font-semibold">
|
||||||
|
<Layers2 size="16" />
|
||||||
|
{$_('layers.label.overlays')}
|
||||||
|
<div class="grow">
|
||||||
|
<Separator />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
<div bind:this={overlayContainer} class="ml-1.5 flex flex-col gap-1">
|
||||||
|
{#each $customOverlayOrder as id (id)}
|
||||||
|
<div class="flex flex-row items-center gap-2" data-id={id}>
|
||||||
|
<Move size="12" />
|
||||||
|
<span class="grow">{$customLayers[id].name}</span>
|
||||||
|
<Button variant="outline" on:click={() => (selectedLayerId = id)} class="p-1 h-7">
|
||||||
|
<Pencil size="16" />
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline" on:click={() => deleteLayer(id)} class="p-1 h-7">
|
||||||
|
<Trash2 size="16" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Card.Root>
|
<Card.Root>
|
||||||
<Card.Header class="p-3">
|
<Card.Header class="p-3">
|
||||||
@@ -301,3 +410,4 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
</Card.Root>
|
</Card.Root>
|
||||||
|
</div>
|
||||||
|
@@ -93,6 +93,8 @@ export const settings = {
|
|||||||
selectedOverpassTree: dexieSettingStore('selectedOverpassTree', defaultOverpassTree),
|
selectedOverpassTree: dexieSettingStore('selectedOverpassTree', defaultOverpassTree),
|
||||||
opacities: dexieSettingStore('opacities', defaultOpacities),
|
opacities: dexieSettingStore('opacities', defaultOpacities),
|
||||||
customLayers: dexieSettingStore<Record<string, CustomLayer>>('customLayers', {}),
|
customLayers: dexieSettingStore<Record<string, CustomLayer>>('customLayers', {}),
|
||||||
|
customBasemapOrder: dexieSettingStore<string[]>('customBasemapOrder', []),
|
||||||
|
customOverlayOrder: dexieSettingStore<string[]>('customOverlayOrder', []),
|
||||||
directionMarkers: dexieSettingStore('directionMarkers', false),
|
directionMarkers: dexieSettingStore('directionMarkers', false),
|
||||||
distanceMarkers: dexieSettingStore('distanceMarkers', false),
|
distanceMarkers: dexieSettingStore('distanceMarkers', false),
|
||||||
stravaHeatmapColor: dexieSettingStore('stravaHeatmapColor', 'bluered'),
|
stravaHeatmapColor: dexieSettingStore('stravaHeatmapColor', 'bluered'),
|
||||||
|
Reference in New Issue
Block a user