your personal website on atproto - mirror
blento.app
1<script lang="ts">
2 import type { Item } from '$lib/types';
3 import { MapLibre, Projection, Marker } from 'svelte-maplibre-gl';
4 import type maplibregl from 'maplibre-gl';
5
6 let { item = $bindable(), isEditing = false }: { item: Item; isEditing?: boolean } = $props();
7
8 let center = $state({ lng: parseFloat(item.cardData.lon), lat: parseFloat(item.cardData.lat) });
9 let showAttribution = $state(false);
10 let map: maplibregl.Map | undefined = $state();
11
12 const fixedCenter = { lng: parseFloat(item.cardData.lon), lat: parseFloat(item.cardData.lat) };
13
14 function handleZoom() {
15 if (!isEditing && map) {
16 map.setCenter(fixedCenter);
17 }
18 }
19
20 $effect(() => {
21 if (!isEditing && map) {
22 map.getCanvas().style.touchAction = 'pan-x pan-y';
23 }
24 });
25</script>
26
27<div
28 class="absolute inset-0 isolate h-full w-full"
29 onfocusin={(e) => {
30 if (!isEditing && e.target instanceof HTMLElement) e.target.blur();
31 }}
32>
33 <div class="h-full w-full">
34 <MapLibre
35 bind:map
36 class="h-full w-full"
37 style="https://tiles.openfreemap.org/styles/liberty"
38 zoom={item.cardData.zoom}
39 {center}
40 attributionControl={false}
41 dragPan={isEditing}
42 dragRotate={false}
43 keyboard={false}
44 touchZoomRotate={true}
45 scrollZoom={true}
46 boxZoom={false}
47 pitchWithRotate={false}
48 touchPitch={false}
49 onzoom={handleZoom}
50 >
51 <Projection type="globe" />
52
53 <Marker bind:lnglat={center}>
54 {#snippet content()}
55 <div class="from-accent-400 size-10 rounded-full bg-radial via-transparent p-3">
56 <div class="bg-accent-500 size-4 rounded-full ring-2 ring-white"></div>
57 </div>
58 {/snippet}
59 </Marker>
60 </MapLibre>
61 </div>
62
63 {#snippet infoIcon()}
64 <svg
65 xmlns="http://www.w3.org/2000/svg"
66 width="24"
67 height="24"
68 fill-rule="evenodd"
69 viewBox="0 0 20 20"
70 >
71 <path
72 d="M4 10a6 6 0 1 0 12 0 6 6 0 1 0-12 0m5-3a1 1 0 1 0 2 0 1 1 0 1 0-2 0m0 3a1 1 0 1 1 2 0v3a1 1 0 1 1-2 0"
73 />
74 </svg>
75 {/snippet}
76
77 {#snippet attributionText()}
78 <a
79 href="https://openfreemap.org"
80 target="_blank"
81 rel="noopener noreferrer"
82 class="text-black/75 no-underline hover:underline"
83 onclick={(e) => e.stopPropagation()}>OpenFreeMap</a
84 >
85 <a
86 href="https://openmaptiles.org"
87 target="_blank"
88 rel="noopener noreferrer"
89 class="text-black/75 no-underline hover:underline"
90 onclick={(e) => e.stopPropagation()}>© OpenMapTiles</a
91 >
92 Data from
93 <a
94 href="https://www.openstreetmap.org/copyright"
95 target="_blank"
96 rel="noopener noreferrer"
97 class="text-black/75 no-underline hover:underline"
98 onclick={(e) => e.stopPropagation()}>OpenStreetMap</a
99 >
100 {/snippet}
101
102 {#if showAttribution}
103 <div
104 class="absolute right-2.5 bottom-2.5 z-10 rounded-xl bg-white text-black"
105 style="width: calc(100% - 20px); max-width: 12rem;"
106 >
107 <button
108 class="float-right flex size-6 cursor-pointer items-center justify-center rounded-full shadow-[0_0_6px_rgba(59,130,246,0.6)]"
109 onclick={() => (showAttribution = false)}
110 aria-label="Toggle attribution"
111 >
112 {@render infoIcon()}
113 </button>
114 <div class="p-2 text-left text-xs leading-snug text-black/75">
115 {@render attributionText()}
116 </div>
117 </div>
118 {:else}
119 <button
120 class="absolute right-2.5 bottom-2.5 z-10 flex size-6 items-center justify-center rounded-full bg-white text-black"
121 onclick={() => (showAttribution = true)}
122 aria-label="Toggle attribution"
123 >
124 {@render infoIcon()}
125 </button>
126 {/if}
127</div>