@versatiles/svelte 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/components/BBoxMap/AutoComplete.svelte +116 -89
  2. package/dist/components/BBoxMap/BBoxMap.svelte +52 -42
  3. package/dist/components/BBoxMap/BBoxMap.svelte.d.ts +5 -3
  4. package/dist/components/BasicMap/BasicMap.svelte +59 -34
  5. package/dist/components/LocatorMap/LocatorMap.svelte +67 -60
  6. package/dist/components/MapEditor/MapEditor.svelte +35 -80
  7. package/dist/components/MapEditor/components/Editor.svelte +53 -0
  8. package/dist/components/MapEditor/{Editor.svelte.d.ts → components/Editor.svelte.d.ts} +1 -1
  9. package/dist/components/MapEditor/components/EditorFill.svelte +28 -0
  10. package/dist/components/MapEditor/components/EditorFill.svelte.d.ts +7 -0
  11. package/dist/components/MapEditor/components/EditorStroke.svelte +28 -0
  12. package/dist/components/MapEditor/components/EditorStroke.svelte.d.ts +7 -0
  13. package/dist/components/MapEditor/components/EditorSymbol.svelte +43 -0
  14. package/dist/components/MapEditor/components/EditorSymbol.svelte.d.ts +7 -0
  15. package/dist/components/MapEditor/components/Sidebar.svelte +179 -0
  16. package/dist/components/MapEditor/components/Sidebar.svelte.d.ts +8 -0
  17. package/dist/components/MapEditor/components/SymbolSelector.svelte +118 -0
  18. package/dist/components/MapEditor/components/SymbolSelector.svelte.d.ts +8 -0
  19. package/dist/components/MapEditor/lib/__mocks__/cursor.d.ts +5 -0
  20. package/dist/components/MapEditor/lib/__mocks__/cursor.js +6 -0
  21. package/dist/components/MapEditor/lib/__mocks__/geometry_manager.d.ts +22 -0
  22. package/dist/components/MapEditor/lib/__mocks__/geometry_manager.js +21 -0
  23. package/dist/components/MapEditor/lib/__mocks__/map.d.ts +36 -0
  24. package/dist/components/MapEditor/lib/__mocks__/map.js +26 -0
  25. package/dist/components/MapEditor/lib/cursor.d.ts +9 -0
  26. package/dist/components/MapEditor/lib/cursor.js +31 -0
  27. package/dist/components/MapEditor/lib/element/abstract.d.ts +21 -0
  28. package/dist/components/MapEditor/lib/element/abstract.js +39 -0
  29. package/dist/components/MapEditor/lib/element/abstract_path.d.ts +11 -0
  30. package/dist/components/MapEditor/lib/element/abstract_path.js +79 -0
  31. package/dist/components/MapEditor/lib/element/line.d.ts +16 -0
  32. package/dist/components/MapEditor/lib/element/line.js +53 -0
  33. package/dist/components/MapEditor/lib/element/marker.d.ts +18 -0
  34. package/dist/components/MapEditor/lib/element/marker.js +62 -0
  35. package/dist/components/MapEditor/lib/element/polygon.d.ts +17 -0
  36. package/dist/components/MapEditor/lib/element/polygon.js +63 -0
  37. package/dist/components/MapEditor/lib/element/types.d.ts +11 -0
  38. package/dist/components/MapEditor/lib/geometry_manager.d.ts +20 -10
  39. package/dist/components/MapEditor/lib/geometry_manager.js +158 -57
  40. package/dist/components/MapEditor/lib/map_layer/abstract.d.ts +30 -0
  41. package/dist/components/MapEditor/lib/map_layer/abstract.js +90 -0
  42. package/dist/components/MapEditor/lib/map_layer/fill.d.ts +24 -0
  43. package/dist/components/MapEditor/lib/map_layer/fill.js +104 -0
  44. package/dist/components/MapEditor/lib/map_layer/line.d.ts +20 -0
  45. package/dist/components/MapEditor/lib/map_layer/line.js +90 -0
  46. package/dist/components/MapEditor/lib/map_layer/symbol.d.ts +19 -0
  47. package/dist/components/MapEditor/lib/map_layer/symbol.js +123 -0
  48. package/dist/components/MapEditor/lib/{types.d.ts → map_layer/types.d.ts} +7 -15
  49. package/dist/components/MapEditor/lib/map_layer/types.js +1 -0
  50. package/dist/components/MapEditor/lib/state/reader.d.ts +17 -0
  51. package/dist/components/MapEditor/lib/state/reader.js +161 -0
  52. package/dist/components/MapEditor/lib/state/types.d.ts +20 -0
  53. package/dist/components/MapEditor/lib/state/types.js +1 -0
  54. package/dist/components/MapEditor/lib/state/writer.d.ts +17 -0
  55. package/dist/components/MapEditor/lib/state/writer.js +178 -0
  56. package/dist/components/MapEditor/lib/symbols.d.ts +16 -0
  57. package/dist/components/MapEditor/lib/symbols.js +173 -0
  58. package/dist/components/MapEditor/lib/utils.d.ts +8 -1
  59. package/dist/components/MapEditor/lib/utils.js +33 -2
  60. package/dist/utils/draw/bbox.d.ts +3 -1
  61. package/dist/utils/draw/bbox.js +56 -51
  62. package/package.json +16 -16
  63. package/dist/components/MapEditor/Editor.svelte +0 -25
  64. package/dist/components/MapEditor/EditorLine.svelte +0 -27
  65. package/dist/components/MapEditor/EditorLine.svelte.d.ts +0 -7
  66. package/dist/components/MapEditor/EditorMarker.svelte +0 -42
  67. package/dist/components/MapEditor/EditorMarker.svelte.d.ts +0 -7
  68. package/dist/components/MapEditor/editor.scss +0 -16
  69. package/dist/components/MapEditor/lib/element_abstract.d.ts +0 -20
  70. package/dist/components/MapEditor/lib/element_abstract.js +0 -58
  71. package/dist/components/MapEditor/lib/element_line.d.ts +0 -14
  72. package/dist/components/MapEditor/lib/element_line.js +0 -76
  73. package/dist/components/MapEditor/lib/element_marker.d.ts +0 -20
  74. package/dist/components/MapEditor/lib/element_marker.js +0 -191
  75. package/dist/components/MapEditor/lib/map_layer.d.ts +0 -14
  76. package/dist/components/MapEditor/lib/map_layer.js +0 -61
  77. package/dist/utils/sprite_library.d.ts +0 -19
  78. package/dist/utils/sprite_library.js +0 -30
  79. /package/dist/components/MapEditor/lib/{types.js → element/types.js} +0 -0
@@ -1,93 +1,120 @@
1
1
  <!-- AutoComplete.svelte -->
2
- <script lang="ts" generics="T">import { isDarkMode } from '../../utils/map_style.js';
3
- /* eslint svelte/no-at-html-tags: off */
4
- let { change, initialInputText, inputText = $bindable(''), items = [], maxItems = 10, minChar = 3, placeholder = '' } = $props();
5
- import { onMount } from 'svelte';
6
- let inputElement; // Reference to DOM element
7
- let autocompleteElement; // Reference to DOM element
8
- let isOpen = $state(false);
9
- let results = $state([]);
10
- let selectedIndex = $state(0);
11
- // Escape special characters in search string for use in regex
12
- const regExpEscape = (s) => s.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&');
13
- if (inputText.length >= minChar) {
14
- const r = filterResults();
15
- if (r.length > 0) {
16
- const { key, value } = r[0];
17
- inputText = key;
18
- change(JSON.parse(JSON.stringify(value)));
19
- }
20
- else {
21
- inputText = '';
22
- }
23
- }
24
- // Handle input change
25
- function onChange() {
26
- if (inputText.length >= minChar) {
27
- results = filterResults();
28
- selectedIndex = 0;
29
- isOpen = true;
30
- }
31
- else {
32
- isOpen = false;
33
- }
34
- }
35
- function onFocus() {
36
- inputElement.setSelectionRange(0, 1000);
37
- }
38
- // Filter results based on search query
39
- function filterResults() {
40
- const searchText = inputText.trim();
41
- const searchTextUpper = searchText.toUpperCase();
42
- const searchReg = RegExp(regExpEscape(searchText), 'i');
43
- return items
44
- .filter((item) => item.key.toUpperCase().includes(searchTextUpper))
45
- .slice(0, maxItems)
46
- .map((item) => ({
47
- ...item,
48
- _label: item.key.replace(searchReg, '<span>$&</span>')
49
- }));
50
- }
51
- // Handle keyboard navigation
52
- function onKeyDown(event) {
53
- switch (event.key) {
54
- case 'ArrowDown':
55
- if (selectedIndex < results.length - 1)
56
- selectedIndex += 1;
57
- break;
58
- case 'ArrowUp':
59
- if (selectedIndex > 0)
60
- selectedIndex -= 1;
61
- break;
62
- case 'Enter':
63
- event.preventDefault();
64
- close(selectedIndex);
65
- break;
66
- case 'Escape':
67
- event.preventDefault();
68
- close();
69
- break;
70
- }
71
- }
72
- // Close the autocomplete and select an item
73
- function close(index = -1) {
74
- isOpen = false;
75
- if (index >= 0 && results[index]) {
76
- const { key, value } = results[index];
77
- inputText = key;
78
- change(JSON.parse(JSON.stringify(value)));
79
- }
80
- }
81
- onMount(() => {
82
- const darkMode = isDarkMode(autocompleteElement);
83
- autocompleteElement.style.setProperty('--bg-color', darkMode ? '#000' : '#fff');
84
- autocompleteElement.style.setProperty('--fg-color', darkMode ? '#fff' : '#000');
85
- if (initialInputText) {
86
- inputText = initialInputText;
87
- results = filterResults();
88
- close(0);
89
- }
90
- });
2
+ <script lang="ts" generics="T">
3
+ import { isDarkMode } from '../../utils/map_style.js';
4
+ /* eslint svelte/no-at-html-tags: off */
5
+
6
+ let {
7
+ change,
8
+ initialInputText,
9
+ inputText = $bindable(''),
10
+ items = [],
11
+ maxItems = 10,
12
+ minChar = 3,
13
+ placeholder = ''
14
+ }: {
15
+ change: (value: T) => void;
16
+ initialInputText?: string;
17
+ inputText?: string;
18
+ items?: Item[];
19
+ maxItems?: number;
20
+ minChar?: number;
21
+ placeholder?: string;
22
+ } = $props();
23
+
24
+ import { onMount } from 'svelte';
25
+
26
+ type Item = { key: string; value: T };
27
+ type ResultItem = Item & { _label: string };
28
+
29
+ let inputElement: HTMLInputElement; // Reference to DOM element
30
+ let autocompleteElement: HTMLDivElement; // Reference to DOM element
31
+ let isOpen = $state(false);
32
+ let results: ResultItem[] = $state([]);
33
+ let selectedIndex = $state(0);
34
+
35
+ // Escape special characters in search string for use in regex
36
+ const regExpEscape = (s: string) => s.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&');
37
+
38
+ if (inputText.length >= minChar) {
39
+ const r = filterResults();
40
+ if (r.length > 0) {
41
+ const { key, value } = r[0];
42
+ inputText = key;
43
+ change(JSON.parse(JSON.stringify(value)));
44
+ } else {
45
+ inputText = '';
46
+ }
47
+ }
48
+
49
+ // Handle input change
50
+ function onChange() {
51
+ if (inputText.length >= minChar) {
52
+ results = filterResults();
53
+ selectedIndex = 0;
54
+ isOpen = true;
55
+ } else {
56
+ isOpen = false;
57
+ }
58
+ }
59
+
60
+ function onFocus() {
61
+ inputElement.setSelectionRange(0, 1000);
62
+ }
63
+
64
+ // Filter results based on search query
65
+ function filterResults(): ResultItem[] {
66
+ const searchText = inputText.trim();
67
+ const searchTextUpper = searchText.toUpperCase();
68
+ const searchReg = RegExp(regExpEscape(searchText), 'i');
69
+ return items
70
+ .filter((item) => item.key.toUpperCase().includes(searchTextUpper))
71
+ .slice(0, maxItems)
72
+ .map((item) => ({
73
+ ...item,
74
+ _label: item.key.replace(searchReg, '<span>$&</span>')
75
+ }));
76
+ }
77
+
78
+ // Handle keyboard navigation
79
+ function onKeyDown(event: KeyboardEvent) {
80
+ switch (event.key) {
81
+ case 'ArrowDown':
82
+ if (selectedIndex < results.length - 1) selectedIndex += 1;
83
+ break;
84
+ case 'ArrowUp':
85
+ if (selectedIndex > 0) selectedIndex -= 1;
86
+ break;
87
+ case 'Enter':
88
+ event.preventDefault();
89
+ close(selectedIndex);
90
+ break;
91
+ case 'Escape':
92
+ event.preventDefault();
93
+ close();
94
+ break;
95
+ }
96
+ }
97
+
98
+ // Close the autocomplete and select an item
99
+ function close(index = -1) {
100
+ isOpen = false;
101
+ if (index >= 0 && results[index]) {
102
+ const { key, value } = results[index];
103
+ inputText = key;
104
+ change(JSON.parse(JSON.stringify(value)));
105
+ }
106
+ }
107
+
108
+ onMount(() => {
109
+ const darkMode = isDarkMode(autocompleteElement);
110
+ autocompleteElement.style.setProperty('--bg-color', darkMode ? '#000' : '#fff');
111
+ autocompleteElement.style.setProperty('--fg-color', darkMode ? '#fff' : '#000');
112
+ if (initialInputText) {
113
+ inputText = initialInputText;
114
+ results = filterResults();
115
+ close(0);
116
+ }
117
+ });
91
118
  </script>
92
119
 
93
120
  <svelte:window on:click={() => close()} />
@@ -1,44 +1,54 @@
1
1
  <!-- BBoxMap.svelte -->
2
- <script lang="ts">import 'maplibre-gl/dist/maplibre-gl.css';
3
- import AutoComplete from './AutoComplete.svelte';
4
- import { getCountryName } from '../../utils/location.js';
5
- import BasicMap from '../BasicMap/BasicMap.svelte';
6
- import { isDarkMode } from '../../utils/map_style.js';
7
- import { loadBBoxes } from './BBoxMap.js';
8
- import { BBoxDrawer } from '../../utils/draw/bbox.js';
9
- let { selectedBBox = $bindable() } = $props();
10
- const startTime = Date.now();
11
- let bbox;
12
- let map = $state();
13
- let bboxes = $state();
14
- let mapContainer;
15
- function onMapInit(_map) {
16
- map = _map;
17
- mapContainer = map.getContainer();
18
- map.setPadding({ top: 31 + 5, right: 5, bottom: 5, left: 5 });
19
- map.on('load', async () => {
20
- bbox = new BBoxDrawer(map, [-180, -86, 180, 86], isDarkMode(mapContainer) ? '#FFFFFF' : '#000000');
21
- bboxes = await loadBBoxes();
22
- });
23
- }
24
- function setBBox(newBBox) {
25
- selectedBBox = newBBox;
26
- if (bbox && map) {
27
- bbox.setGeometry(newBBox);
28
- const transform = map.cameraForBounds(bbox.getBounds());
29
- if (transform == null)
30
- return;
31
- transform.zoom = transform.zoom ?? 0 - 0.5;
32
- transform.bearing = 0;
33
- transform.pitch = 0;
34
- if (Date.now() - startTime < 1000) {
35
- map.jumpTo(transform);
36
- }
37
- else {
38
- map.flyTo({ ...transform, essential: true, speed: 5 });
39
- }
40
- }
41
- }
2
+ <script lang="ts">
3
+ import type { CameraOptions, Map as MaplibreMapType } from 'maplibre-gl';
4
+ import 'maplibre-gl/dist/maplibre-gl.css';
5
+ import AutoComplete from './AutoComplete.svelte';
6
+ import { getCountryName } from '../../utils/location.js';
7
+ import BasicMap from '../BasicMap/BasicMap.svelte';
8
+ import { isDarkMode } from '../../utils/map_style.js';
9
+ import type { BBox } from 'geojson';
10
+ import { loadBBoxes } from './BBoxMap.js';
11
+ import { BBoxDrawer } from '../../utils/draw/bbox.js';
12
+
13
+ let { selectedBBox = $bindable() }: { selectedBBox?: BBox } = $props();
14
+ const startTime = Date.now();
15
+ let bboxDrawer: BBoxDrawer;
16
+ let map: MaplibreMapType | undefined = $state();
17
+ let bboxes: { key: string; value: BBox }[] | undefined = $state();
18
+ let mapContainer: HTMLElement;
19
+
20
+ async function onMapLoad(_map: MaplibreMapType) {
21
+ map = _map;
22
+ mapContainer = map.getContainer();
23
+ map.setPadding({ top: 31 + 5, right: 5, bottom: 5, left: 5 });
24
+ bboxDrawer = new BBoxDrawer(
25
+ map!,
26
+ [-180, -86, 180, 86],
27
+ isDarkMode(mapContainer) ? '#FFFFFF' : '#000000'
28
+ );
29
+ bboxes = await loadBBoxes();
30
+ bboxDrawer.bbox.subscribe((bbox) => {
31
+ selectedBBox = bbox;
32
+ });
33
+ }
34
+
35
+ function flyToBBox(bbox: BBox) {
36
+ if (!map || !bbox) return;
37
+
38
+ bboxDrawer.setGeometry(bbox);
39
+
40
+ const transform = map.cameraForBounds(bboxDrawer.getBounds()) as CameraOptions;
41
+ if (transform == null) return;
42
+ transform.zoom = transform.zoom ?? 0 - 0.5;
43
+ transform.bearing = 0;
44
+ transform.pitch = 0;
45
+
46
+ if (Date.now() - startTime < 1000) {
47
+ map.jumpTo(transform);
48
+ } else {
49
+ map.flyTo({ ...transform, essential: true, speed: 5 });
50
+ }
51
+ }
42
52
  </script>
43
53
 
44
54
  <div class="container">
@@ -47,12 +57,12 @@ function setBBox(newBBox) {
47
57
  <AutoComplete
48
58
  items={bboxes}
49
59
  placeholder="Find country, region or city …"
50
- change={setBBox}
60
+ change={(bbox) => flyToBBox(bbox)}
51
61
  initialInputText={getCountryName() ?? ''}
52
62
  />
53
63
  </div>
54
64
  {/if}
55
- <BasicMap {onMapInit}></BasicMap>
65
+ <BasicMap {onMapLoad}></BasicMap>
56
66
  </div>
57
67
 
58
68
  <style>
@@ -1,6 +1,8 @@
1
1
  import 'maplibre-gl/dist/maplibre-gl.css';
2
- declare const BBoxMap: import("svelte").Component<{
3
- selectedBBox?: any;
4
- }, {}, "selectedBBox">;
2
+ import type { BBox } from 'geojson';
3
+ type $$ComponentProps = {
4
+ selectedBBox?: BBox;
5
+ };
6
+ declare const BBoxMap: import("svelte").Component<$$ComponentProps, {}, "selectedBBox">;
5
7
  type BBoxMap = ReturnType<typeof BBoxMap>;
6
8
  export default BBoxMap;
@@ -1,43 +1,68 @@
1
1
  <!-- BasicMap.svelte -->
2
- <script lang="ts">import 'maplibre-gl/dist/maplibre-gl.css';
3
- import { getMapStyle, isDarkMode } from '../../utils/map_style.js';
4
- // Props
5
- let { style = 'position:absolute; left:0px; top:0px; width:100%; height:100%;', styleOptions = { transitionDuration: 0 }, mapOptions = {}, map = $bindable(), onMapInit, onMapLoad } = $props();
6
- let container;
7
- $effect(() => {
8
- if (container)
9
- init();
10
- });
11
- async function init() {
12
- if (map)
13
- return;
14
- let MaplibreMap = (await import('maplibre-gl')).Map;
15
- if (!container)
16
- throw Error();
17
- const darkMode = isDarkMode(container);
18
- container.style.setProperty('--bg-color', darkMode ? '#000' : '#fff');
19
- container.style.setProperty('--fg-color', darkMode ? '#fff' : '#000');
20
- map = new MaplibreMap({
21
- container,
22
- style: getMapStyle(darkMode, styleOptions),
23
- renderWorldCopies: false,
24
- dragRotate: false,
25
- attributionControl: { compact: false },
26
- fadeDuration: 0,
27
- ...mapOptions
28
- });
29
- if (onMapInit)
30
- onMapInit(map);
31
- map.on('load', () => {
32
- if (onMapLoad)
33
- onMapLoad(map);
34
- });
35
- }
2
+ <script lang="ts">
3
+ import type { Map as MaplibreMapType, MapOptions } from 'maplibre-gl';
4
+ import 'maplibre-gl/dist/maplibre-gl.css';
5
+ import { getMapStyle, isDarkMode } from '../../utils/map_style.js';
6
+ import { Map as MaplibreMap } from 'maplibre-gl';
7
+
8
+ // Props
9
+ let {
10
+ style = 'position:absolute; left:0px; top:0px; width:100%; height:100%;',
11
+ styleOptions = { transitionDuration: 0 },
12
+ mapOptions = {},
13
+ map = $bindable(),
14
+ onMapInit,
15
+ onMapLoad
16
+ }: {
17
+ style?: string;
18
+ styleOptions?: Parameters<typeof getMapStyle>[1];
19
+ mapOptions?: Partial<MapOptions>;
20
+ map?: MaplibreMapType;
21
+ onMapInit?: (map: MaplibreMapType) => void;
22
+ onMapLoad?: (map: MaplibreMapType) => void;
23
+ } = $props();
24
+
25
+ let container: HTMLDivElement;
26
+
27
+ $effect(() => {
28
+ if (container) init();
29
+ });
30
+
31
+ async function init(): Promise<void> {
32
+ if (map) return;
33
+
34
+ if (!container) throw Error();
35
+
36
+ const darkMode = isDarkMode(container);
37
+ container.style.setProperty('--bg-color', darkMode ? '#000' : '#fff');
38
+ container.style.setProperty('--fg-color', darkMode ? '#fff' : '#000');
39
+
40
+ const style = getMapStyle(darkMode, styleOptions);
41
+ style.transition = { duration: 0, delay: 0 };
42
+ map = new MaplibreMap({
43
+ container,
44
+ style,
45
+ renderWorldCopies: false,
46
+ dragRotate: false,
47
+ attributionControl: { compact: false },
48
+ fadeDuration: 0,
49
+ ...mapOptions
50
+ });
51
+
52
+ if (onMapInit) onMapInit(map);
53
+
54
+ map.on('load', () => {
55
+ if (onMapLoad) onMapLoad(map!);
56
+ });
57
+ }
36
58
  </script>
37
59
 
38
60
  <div class="map" {style} bind:this={container}></div>
39
61
 
40
62
  <style>
63
+ .map :global(canvas) {
64
+ outline: none !important;
65
+ }
41
66
  .map :global(.maplibregl-ctrl-attrib) {
42
67
  background-color: color-mix(in srgb, var(--bg-color) 50%, transparent) !important;
43
68
  color: var(--fg-color) !important;
@@ -1,64 +1,71 @@
1
1
  <!-- LocatorMap.svelte -->
2
- <script lang="ts">import 'maplibre-gl/dist/maplibre-gl.css';
3
- import BasicMap from '../BasicMap/BasicMap.svelte';
4
- let map;
5
- function onMapInit(_map) {
6
- map = _map;
7
- map.on('load', async () => {
8
- let coordinates = [0, 0];
9
- const initialHash = parseHash();
10
- if (initialHash) {
11
- console.log('initialHash', initialHash);
12
- coordinates = [initialHash[1], initialHash[2]];
13
- map.setZoom(initialHash[0]);
14
- map.setCenter(coordinates);
15
- }
16
- else {
17
- map.on('move', () => {
18
- const { lng, lat } = map.getCenter();
19
- source.setData({
20
- type: 'Feature',
21
- geometry: { type: 'Point', coordinates: [lng, lat] },
22
- properties: {}
23
- });
24
- });
25
- map.on('moveend', () => updateHash());
26
- }
27
- map.addSource('marker', {
28
- type: 'geojson',
29
- data: { type: 'Feature', geometry: { type: 'Point', coordinates }, properties: {} }
30
- });
31
- const source = map.getSource('marker');
32
- map.addLayer({
33
- id: 'marker',
34
- source: 'marker',
35
- type: 'symbol',
36
- layout: {
37
- 'icon-image': 'basics:icon-embassy',
38
- 'icon-size': 1,
39
- 'icon-overlap': 'always'
40
- },
41
- paint: {
42
- 'icon-color': '#FF0000',
43
- 'icon-halo-color': '#FFFFFF',
44
- 'icon-halo-width': 1,
45
- 'icon-halo-blur': 0
46
- }
47
- });
48
- function parseHash() {
49
- const hash = window.location.hash
50
- .replace(/[^0-9/.]+/g, '')
51
- .split('/')
52
- .map(parseFloat);
53
- return hash.length === 3 ? hash : undefined;
54
- }
55
- function updateHash() {
56
- const center = map.getCenter();
57
- const zoom = map.getZoom();
58
- window.location.hash = `#${zoom.toFixed(2)}/${center.lng.toFixed(6)}/${center.lat.toFixed(6)}`;
59
- }
60
- });
61
- }
2
+ <script lang="ts">
3
+ import type { Map as MaplibreMapType, GeoJSONSource } from 'maplibre-gl';
4
+ import 'maplibre-gl/dist/maplibre-gl.css';
5
+ import BasicMap from '../BasicMap/BasicMap.svelte';
6
+ let map: MaplibreMapType;
7
+
8
+ function onMapInit(_map: MaplibreMapType) {
9
+ map = _map;
10
+ map.on('load', async () => {
11
+ let coordinates: [number, number] = [0, 0];
12
+ const initialHash = parseHash();
13
+ if (initialHash) {
14
+ console.log('initialHash', initialHash);
15
+ coordinates = [initialHash[1], initialHash[2]];
16
+ map.setZoom(initialHash[0]);
17
+ map.setCenter(coordinates);
18
+ } else {
19
+ map.on('move', () => {
20
+ const { lng, lat } = map.getCenter();
21
+ source.setData({
22
+ type: 'Feature',
23
+ geometry: { type: 'Point', coordinates: [lng, lat] },
24
+ properties: {}
25
+ });
26
+ });
27
+ map.on('moveend', () => updateHash());
28
+ }
29
+
30
+ map.addSource('marker', {
31
+ type: 'geojson',
32
+ data: { type: 'Feature', geometry: { type: 'Point', coordinates }, properties: {} }
33
+ });
34
+
35
+ const source = map.getSource('marker') as GeoJSONSource;
36
+
37
+ map.addLayer({
38
+ id: 'marker',
39
+ source: 'marker',
40
+ type: 'symbol',
41
+ layout: {
42
+ 'icon-image': 'basics:icon-embassy',
43
+ 'icon-size': 1,
44
+ 'icon-overlap': 'always'
45
+ },
46
+ paint: {
47
+ 'icon-color': '#FF0000',
48
+ 'icon-halo-color': '#FFFFFF',
49
+ 'icon-halo-width': 1,
50
+ 'icon-halo-blur': 0
51
+ }
52
+ });
53
+
54
+ function parseHash(): [number, number, number] | undefined {
55
+ const hash = window.location.hash
56
+ .replace(/[^0-9/.]+/g, '')
57
+ .split('/')
58
+ .map(parseFloat);
59
+ return hash.length === 3 ? (hash as [number, number, number]) : undefined;
60
+ }
61
+
62
+ function updateHash() {
63
+ const center = map.getCenter();
64
+ const zoom = map.getZoom();
65
+ window.location.hash = `#${zoom.toFixed(2)}/${center.lng.toFixed(6)}/${center.lat.toFixed(6)}`;
66
+ }
67
+ });
68
+ }
62
69
  </script>
63
70
 
64
71
  <div class="container">