split custom basemaps and overlays, and allow reordering

This commit is contained in:
vcoppe
2024-07-25 18:48:11 +02:00
parent 8fb41837cc
commit 7d7d312476
2 changed files with 200 additions and 88 deletions

View File

@@ -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,96 +283,131 @@
$: 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')}
<Pencil size="16" /> <div class="grow">
</Button> <Separator />
<Button variant="outline" on:click={() => deleteLayer(id)} class="p-1 h-8"> </div>
<Trash2 size="16" />
</Button>
</div> </div>
{/each} {/if}
</div> <div bind:this={basemapContainer} class="ml-1.5 flex flex-col gap-1">
{/if} {#each $customBasemapOrder as id (id)}
<div class="flex flex-row items-center gap-2" data-id={id}>
<Card.Root> <Move size="12" />
<Card.Header class="p-3"> <span class="grow">{$customLayers[id].name}</span>
<Card.Title class="text-base"> <Button variant="outline" on:click={() => (selectedLayerId = id)} class="p-1 h-7">
{#if selectedLayerId} <Pencil size="16" />
{$_('layers.custom_layers.edit')} </Button>
{:else} <Button variant="outline" on:click={() => deleteLayer(id)} class="p-1 h-7">
{$_('layers.custom_layers.new')} <Trash2 size="16" />
{/if} </Button>
</Card.Title>
</Card.Header>
<Card.Content class="p-3 pt-0">
<fieldset class="flex flex-col gap-2">
<Label for="name">{$_('menu.metadata.name')}</Label>
<Input bind:value={name} id="name" class="h-8" />
<Label for="url">{$_('layers.custom_layers.urls')}</Label>
{#each tileUrls as url, i}
<div class="flex flex-row gap-2">
<Input
bind:value={tileUrls[i]}
id="url"
class="h-8"
placeholder={$_('layers.custom_layers.url_placeholder')}
/>
{#if tileUrls.length > 1}
<Button
on:click={() => (tileUrls = tileUrls.filter((_, index) => index !== i))}
variant="outline"
class="p-1 h-8"
>
<Minus size="16" />
</Button>
{/if}
{#if i === tileUrls.length - 1}
<Button
on:click={() => (tileUrls = [...tileUrls, ''])}
variant="outline"
class="p-1 h-8"
>
<Plus size="16" />
</Button>
{/if}
</div> </div>
{/each} {/each}
{#if resourceType === 'raster'} </div>
<Label for="maxZoom">{$_('layers.custom_layers.max_zoom')}</Label> {#if $customOverlayOrder.length > 0}
<Input type="number" bind:value={maxZoom} id="maxZoom" min={0} max={22} class="h-8" /> <div class="flex flex-row items-center gap-1 font-semibold">
{/if} <Layers2 size="16" />
<Label>{$_('layers.custom_layers.layer_type')}</Label> {$_('layers.label.overlays')}
<RadioGroup.Root bind:value={layerType} class="flex flex-row"> <div class="grow">
<div class="flex items-center space-x-2"> <Separator />
<RadioGroup.Item value="basemap" id="basemap" />
<Label for="basemap">{$_('layers.custom_layers.basemap')}</Label>
</div> </div>
<div class="flex items-center space-x-2"> </div>
<RadioGroup.Item value="overlay" id="overlay" disabled={resourceType === 'vector'} /> {/if}
<Label for="overlay">{$_('layers.custom_layers.overlay')}</Label> <div bind:this={overlayContainer} class="ml-1.5 flex flex-col gap-1">
</div> {#each $customOverlayOrder as id (id)}
</RadioGroup.Root> <div class="flex flex-row items-center gap-2" data-id={id}>
{#if selectedLayerId} <Move size="12" />
<div class="mt-2 flex flex-row gap-2"> <span class="grow">{$customLayers[id].name}</span>
<Button variant="outline" on:click={createLayer} class="grow"> <Button variant="outline" on:click={() => (selectedLayerId = id)} class="p-1 h-7">
<Save size="16" class="mr-1" /> <Pencil size="16" />
{$_('layers.custom_layers.update')}
</Button> </Button>
<Button variant="outline" on:click={() => (selectedLayerId = undefined)}> <Button variant="outline" on:click={() => deleteLayer(id)} class="p-1 h-7">
<CircleX size="16" /> <Trash2 size="16" />
</Button> </Button>
</div> </div>
{:else} {/each}
<Button variant="outline" class="mt-2" on:click={createLayer}> </div>
<CirclePlus size="16" class="mr-1" /> </div>
{$_('layers.custom_layers.create')}
</Button> <Card.Root>
{/if} <Card.Header class="p-3">
</fieldset> <Card.Title class="text-base">
</Card.Content> {#if selectedLayerId}
</Card.Root> {$_('layers.custom_layers.edit')}
{:else}
{$_('layers.custom_layers.new')}
{/if}
</Card.Title>
</Card.Header>
<Card.Content class="p-3 pt-0">
<fieldset class="flex flex-col gap-2">
<Label for="name">{$_('menu.metadata.name')}</Label>
<Input bind:value={name} id="name" class="h-8" />
<Label for="url">{$_('layers.custom_layers.urls')}</Label>
{#each tileUrls as url, i}
<div class="flex flex-row gap-2">
<Input
bind:value={tileUrls[i]}
id="url"
class="h-8"
placeholder={$_('layers.custom_layers.url_placeholder')}
/>
{#if tileUrls.length > 1}
<Button
on:click={() => (tileUrls = tileUrls.filter((_, index) => index !== i))}
variant="outline"
class="p-1 h-8"
>
<Minus size="16" />
</Button>
{/if}
{#if i === tileUrls.length - 1}
<Button
on:click={() => (tileUrls = [...tileUrls, ''])}
variant="outline"
class="p-1 h-8"
>
<Plus size="16" />
</Button>
{/if}
</div>
{/each}
{#if resourceType === 'raster'}
<Label for="maxZoom">{$_('layers.custom_layers.max_zoom')}</Label>
<Input type="number" bind:value={maxZoom} id="maxZoom" min={0} max={22} class="h-8" />
{/if}
<Label>{$_('layers.custom_layers.layer_type')}</Label>
<RadioGroup.Root bind:value={layerType} class="flex flex-row">
<div class="flex items-center space-x-2">
<RadioGroup.Item value="basemap" id="basemap" />
<Label for="basemap">{$_('layers.custom_layers.basemap')}</Label>
</div>
<div class="flex items-center space-x-2">
<RadioGroup.Item value="overlay" id="overlay" disabled={resourceType === 'vector'} />
<Label for="overlay">{$_('layers.custom_layers.overlay')}</Label>
</div>
</RadioGroup.Root>
{#if selectedLayerId}
<div class="mt-2 flex flex-row gap-2">
<Button variant="outline" on:click={createLayer} class="grow">
<Save size="16" class="mr-1" />
{$_('layers.custom_layers.update')}
</Button>
<Button variant="outline" on:click={() => (selectedLayerId = undefined)}>
<CircleX size="16" />
</Button>
</div>
{:else}
<Button variant="outline" class="mt-2" on:click={createLayer}>
<CirclePlus size="16" class="mr-1" />
{$_('layers.custom_layers.create')}
</Button>
{/if}
</fieldset>
</Card.Content>
</Card.Root>
</div>

View File

@@ -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'),