@versatiles/svelte 2.0.0 → 2.1.0
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.
- package/dist/components/BBoxMap/BBoxMap.svelte +23 -12
- package/dist/components/BBoxMap/lib/bbox.d.ts +1 -1
- package/dist/components/BBoxMap/lib/bbox.js +19 -17
- package/dist/components/BasicMap/BasicMap.svelte +22 -9
- package/dist/components/BasicMap/BasicMap.svelte.d.ts +2 -1
- package/dist/components/MapEditor/MapEditor.svelte +51 -20
- package/dist/components/MapEditor/components/Dialog.svelte +92 -0
- package/dist/components/MapEditor/components/Dialog.svelte.d.ts +17 -0
- package/dist/components/MapEditor/components/DialogFile.svelte +112 -0
- package/dist/components/MapEditor/components/DialogFile.svelte.d.ts +6 -0
- package/dist/components/MapEditor/components/DialogShare.svelte +216 -0
- package/dist/components/MapEditor/components/DialogShare.svelte.d.ts +10 -0
- package/dist/components/MapEditor/components/Editor.svelte +16 -14
- package/dist/components/MapEditor/components/EditorFill.svelte +35 -3
- package/dist/components/MapEditor/components/EditorStroke.svelte +35 -3
- package/dist/components/MapEditor/components/EditorSymbol.svelte +85 -8
- package/dist/components/MapEditor/components/InputRow.svelte +2 -3
- package/dist/components/MapEditor/components/PanelFile.svelte +73 -0
- package/dist/components/MapEditor/components/PanelFile.svelte.d.ts +7 -0
- package/dist/components/MapEditor/components/PanelSymbolSelector.svelte +82 -0
- package/dist/components/MapEditor/components/PanelSymbolSelector.svelte.d.ts +8 -0
- package/dist/components/MapEditor/components/Sidebar.svelte +59 -103
- package/dist/components/MapEditor/components/Sidebar.svelte.d.ts +3 -3
- package/dist/components/MapEditor/components/SidebarPanel.svelte +26 -9
- package/dist/components/MapEditor/lib/element/abstract.d.ts +8 -4
- package/dist/components/MapEditor/lib/element/abstract.js +11 -2
- package/dist/components/MapEditor/lib/element/abstract_path.d.ts +3 -2
- package/dist/components/MapEditor/lib/element/abstract_path.js +6 -3
- package/dist/components/MapEditor/lib/element/circle.d.ts +25 -0
- package/dist/components/MapEditor/lib/element/circle.js +118 -0
- package/dist/components/MapEditor/lib/element/line.d.ts +2 -2
- package/dist/components/MapEditor/lib/element/line.js +1 -1
- package/dist/components/MapEditor/lib/element/marker.d.ts +4 -3
- package/dist/components/MapEditor/lib/element/marker.js +2 -2
- package/dist/components/MapEditor/lib/element/polygon.d.ts +2 -2
- package/dist/components/MapEditor/lib/element/polygon.js +2 -2
- package/dist/components/MapEditor/lib/element/types.d.ts +2 -3
- package/dist/components/MapEditor/lib/geometry_manager.d.ts +12 -29
- package/dist/components/MapEditor/lib/geometry_manager.js +44 -162
- package/dist/components/MapEditor/lib/geometry_manager_interactive.d.ts +33 -0
- package/dist/components/MapEditor/lib/geometry_manager_interactive.js +102 -0
- package/dist/components/MapEditor/lib/map_layer/abstract.d.ts +2 -2
- package/dist/components/MapEditor/lib/map_layer/abstract.js +25 -25
- package/dist/components/MapEditor/lib/map_layer/fill.js +5 -16
- package/dist/components/MapEditor/lib/map_layer/line.js +5 -17
- package/dist/components/MapEditor/lib/map_layer/symbol.js +1 -1
- package/dist/components/MapEditor/lib/selection.d.ts +11 -0
- package/dist/components/MapEditor/lib/selection.js +70 -0
- package/dist/components/MapEditor/lib/state/constants.js +5 -6
- package/dist/components/MapEditor/lib/state/history.d.ts +14 -0
- package/dist/components/MapEditor/lib/state/history.js +53 -0
- package/dist/components/MapEditor/lib/state/manager.d.ts +8 -10
- package/dist/components/MapEditor/lib/state/manager.js +24 -48
- package/dist/components/MapEditor/lib/state/reader.d.ts +6 -4
- package/dist/components/MapEditor/lib/state/reader.js +70 -18
- package/dist/components/MapEditor/lib/state/types.d.ts +19 -2
- package/dist/components/MapEditor/lib/state/utils.d.ts +2 -0
- package/dist/components/MapEditor/lib/state/utils.js +12 -0
- package/dist/components/MapEditor/lib/state/writer.d.ts +6 -4
- package/dist/components/MapEditor/lib/state/writer.js +59 -19
- package/dist/components/MapEditor/lib/symbols.d.ts +1 -1
- package/dist/components/MapEditor/lib/symbols.js +47 -28
- package/dist/components/MapEditor/lib/utils/event_handler.d.ts +10 -0
- package/dist/components/MapEditor/lib/utils/event_handler.js +39 -0
- package/dist/components/MapEditor/lib/utils/geometry.d.ts +12 -0
- package/dist/components/MapEditor/lib/utils/geometry.js +87 -0
- package/dist/components/MapEditor/lib/utils/types.d.ts +2 -0
- package/dist/components/MapEditor/lib/utils/types.js +1 -0
- package/dist/components/MapEditor/style/button.scss +115 -0
- package/dist/components/MapEditor/style/index.scss +3 -0
- package/dist/components/MapEditor/style/layout.scss +20 -0
- package/dist/components/MapEditor/style/other.scss +10 -0
- package/dist/utils/location.d.ts +1 -0
- package/dist/utils/location.js +181 -0
- package/dist/utils/map_style.d.ts +2 -2
- package/dist/utils/map_style.js +2 -2
- package/package.json +29 -29
- package/dist/components/MapEditor/components/SymbolSelector.svelte +0 -110
- package/dist/components/MapEditor/components/SymbolSelector.svelte.d.ts +0 -8
- package/dist/components/MapEditor/lib/utils.d.ts +0 -6
- package/dist/components/MapEditor/lib/utils.js +0 -23
- /package/dist/components/MapEditor/lib/{geocoder.d.ts → utils/geocoder.d.ts} +0 -0
- /package/dist/components/MapEditor/lib/{geocoder.js → utils/geocoder.js} +0 -0
@@ -15,19 +15,15 @@
|
|
15
15
|
let bboxes: { key: string; value: BBox }[] | undefined = $state();
|
16
16
|
let mapContainer: HTMLElement;
|
17
17
|
|
18
|
-
async function
|
18
|
+
async function onMapInit(_map: MaplibreMapType) {
|
19
19
|
map = _map;
|
20
20
|
mapContainer = map.getContainer();
|
21
|
-
map.setPadding({ top:
|
22
|
-
bboxDrawer = new BBoxDrawer(
|
23
|
-
map!,
|
24
|
-
[-180, -86, 180, 86],
|
25
|
-
isDarkMode(mapContainer) ? '#FFFFFF' : '#000000'
|
26
|
-
);
|
21
|
+
map.setPadding({ top: 42, right: 10, bottom: 15, left: 10 });
|
22
|
+
bboxDrawer = new BBoxDrawer(map!, [-180, -85, 180, 85], isDarkMode(mapContainer) ? '#FFFFFF' : '#000000');
|
27
23
|
bboxes = await loadBBoxes();
|
28
|
-
|
29
|
-
|
30
|
-
|
24
|
+
// If an initial bbox is already provided by the parent, display it instead of guessing the user's country
|
25
|
+
if (selectedBBox) flyToBBox(selectedBBox);
|
26
|
+
bboxDrawer.bbox.subscribe((bbox) => (selectedBBox = bbox));
|
31
27
|
}
|
32
28
|
|
33
29
|
function flyToBBox(bbox: BBox) {
|
@@ -47,6 +43,21 @@
|
|
47
43
|
map.flyTo({ ...transform, essential: true, speed: 5 });
|
48
44
|
}
|
49
45
|
}
|
46
|
+
|
47
|
+
function getInitialInputText() {
|
48
|
+
// When an initial bbox is supplied, we skip the country pre‑fill
|
49
|
+
if (selectedBBox) {
|
50
|
+
const area = (selectedBBox[2] - selectedBBox[0]) * (selectedBBox[3] - selectedBBox[1]);
|
51
|
+
if (area < 60000) return '';
|
52
|
+
}
|
53
|
+
let query = getCountryName() ?? '';
|
54
|
+
switch (query) {
|
55
|
+
case 'France':
|
56
|
+
query = 'France, métropolitaine';
|
57
|
+
break;
|
58
|
+
}
|
59
|
+
return query;
|
60
|
+
}
|
50
61
|
</script>
|
51
62
|
|
52
63
|
<div class="container">
|
@@ -56,11 +67,11 @@
|
|
56
67
|
items={bboxes}
|
57
68
|
placeholder="Find country, region or city …"
|
58
69
|
change={(bbox) => flyToBBox(bbox)}
|
59
|
-
initialInputText={
|
70
|
+
initialInputText={getInitialInputText()}
|
60
71
|
/>
|
61
72
|
</div>
|
62
73
|
{/if}
|
63
|
-
<BasicMap {
|
74
|
+
<BasicMap {onMapInit} emptyStyle={true}></BasicMap>
|
64
75
|
</div>
|
65
76
|
|
66
77
|
<style>
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import { get, writable } from 'svelte/store';
|
2
2
|
import maplibregl from 'maplibre-gl';
|
3
|
-
|
3
|
+
import { getMapStyle } from '../../../utils/map_style.js';
|
4
4
|
// prettier-ignore
|
5
5
|
export const DragPointMap = new Map([
|
6
6
|
['n', { cursor: 'ns-resize', flipH: 'n', flipV: 's' }],
|
@@ -15,7 +15,7 @@ export const DragPointMap = new Map([
|
|
15
15
|
]);
|
16
16
|
const worldBBox = [-180, -85, 180, 85];
|
17
17
|
export class BBoxDrawer {
|
18
|
-
|
18
|
+
sourceId;
|
19
19
|
dragPoint = false;
|
20
20
|
isDragging = false;
|
21
21
|
map;
|
@@ -26,27 +26,27 @@ export class BBoxDrawer {
|
|
26
26
|
this.bbox = writable(bbox);
|
27
27
|
this.inverted = inverted ?? true;
|
28
28
|
this.map = map;
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
29
|
+
this.sourceId = 'bbox_' + Math.random().toString(36).slice(2);
|
30
|
+
const style = getMapStyle();
|
31
|
+
style.transition = { duration: 0, delay: 0 };
|
32
|
+
style.sources[this.sourceId] = { type: 'geojson', data: this.getAsFeatureCollection() };
|
33
|
+
style.layers.push({
|
34
34
|
id: 'bbox-line_' + Math.random().toString(36).slice(2),
|
35
35
|
type: 'line',
|
36
|
-
source: sourceId,
|
36
|
+
source: this.sourceId,
|
37
37
|
filter: ['==', '$type', 'LineString'],
|
38
38
|
layout: { 'line-cap': 'round', 'line-join': 'round' },
|
39
39
|
paint: { 'line-color': color }
|
40
40
|
});
|
41
|
-
|
41
|
+
style.layers.push({
|
42
42
|
id: 'bbox-fill_' + Math.random().toString(36).slice(2),
|
43
43
|
type: 'fill',
|
44
|
-
source: sourceId,
|
44
|
+
source: this.sourceId,
|
45
45
|
filter: ['==', '$type', 'Polygon'],
|
46
46
|
layout: {},
|
47
47
|
paint: { 'fill-color': color, 'fill-opacity': 0.2 }
|
48
48
|
});
|
49
|
-
|
49
|
+
map.setStyle(style);
|
50
50
|
this.canvas = map.getCanvasContainer();
|
51
51
|
map.on('mousemove', (e) => {
|
52
52
|
if (e.originalEvent.buttons % 2 === 0)
|
@@ -85,10 +85,7 @@ export class BBoxDrawer {
|
|
85
85
|
const ring = getRing(get(this.bbox));
|
86
86
|
return {
|
87
87
|
type: 'FeatureCollection',
|
88
|
-
features: [
|
89
|
-
this.inverted ? polygon(getRing(worldBBox), ring) : polygon(ring),
|
90
|
-
linestring(ring)
|
91
|
-
]
|
88
|
+
features: [this.inverted ? polygon(getRing(worldBBox), ring) : polygon(ring), linestring(ring)]
|
92
89
|
};
|
93
90
|
function getRing(bbox) {
|
94
91
|
const x0 = Math.min(bbox[0], bbox[2]);
|
@@ -110,10 +107,15 @@ export class BBoxDrawer {
|
|
110
107
|
this.redraw();
|
111
108
|
}
|
112
109
|
getBounds() {
|
113
|
-
return new LngLatBounds(get(this.bbox));
|
110
|
+
return new maplibregl.LngLatBounds(get(this.bbox));
|
114
111
|
}
|
115
112
|
redraw() {
|
116
|
-
this.
|
113
|
+
const source = this.map.getSource(this.sourceId);
|
114
|
+
if (!source)
|
115
|
+
return;
|
116
|
+
if (source instanceof maplibregl.GeoJSONSource) {
|
117
|
+
source.setData(this.getAsFeatureCollection());
|
118
|
+
}
|
117
119
|
}
|
118
120
|
getAsPixel() {
|
119
121
|
const bbox = get(this.bbox);
|
@@ -10,18 +10,21 @@
|
|
10
10
|
styleOptions = { transitionDuration: 0 },
|
11
11
|
mapOptions = {},
|
12
12
|
map = $bindable(),
|
13
|
+
emptyStyle = false,
|
13
14
|
onMapInit,
|
14
15
|
onMapLoad
|
15
16
|
}: {
|
16
17
|
style?: string;
|
17
|
-
styleOptions?: Parameters<typeof getMapStyle>[
|
18
|
+
styleOptions?: Parameters<typeof getMapStyle>[0];
|
18
19
|
mapOptions?: Partial<MapOptions>;
|
19
20
|
map?: maplibre.Map;
|
21
|
+
emptyStyle?: boolean;
|
20
22
|
onMapInit?: (map: maplibre.Map, maplibre: typeof import('maplibre-gl')) => void;
|
21
23
|
onMapLoad?: (map: maplibre.Map, maplibre: typeof import('maplibre-gl')) => void;
|
22
24
|
} = $props();
|
23
25
|
|
24
26
|
let container: HTMLDivElement;
|
27
|
+
let triggeredMapReady = false;
|
25
28
|
|
26
29
|
$effect(() => {
|
27
30
|
if (container) init();
|
@@ -30,14 +33,18 @@
|
|
30
33
|
async function init(): Promise<void> {
|
31
34
|
if (map) return;
|
32
35
|
|
33
|
-
if (
|
36
|
+
if (styleOptions.darkMode == null) styleOptions.darkMode = isDarkMode(container);
|
34
37
|
|
35
|
-
|
36
|
-
container.style.setProperty('--
|
37
|
-
|
38
|
+
container.style.setProperty('--bg-color', styleOptions.darkMode ? '#000' : '#fff');
|
39
|
+
container.style.setProperty('--fg-color', styleOptions.darkMode ? '#fff' : '#000');
|
40
|
+
|
41
|
+
let style = undefined;
|
42
|
+
|
43
|
+
if (!emptyStyle) {
|
44
|
+
style = getMapStyle(styleOptions);
|
45
|
+
style.transition = { duration: 0, delay: 0 };
|
46
|
+
}
|
38
47
|
|
39
|
-
const style = getMapStyle(darkMode, styleOptions);
|
40
|
-
style.transition = { duration: 0, delay: 0 };
|
41
48
|
map = new maplibre.Map({
|
42
49
|
container,
|
43
50
|
style,
|
@@ -50,9 +57,15 @@
|
|
50
57
|
|
51
58
|
if (onMapInit) onMapInit(map, maplibre);
|
52
59
|
|
53
|
-
map.on('
|
60
|
+
map.on('idle', checkMapReady);
|
61
|
+
|
62
|
+
function checkMapReady() {
|
63
|
+
if (triggeredMapReady) return;
|
64
|
+
if (!map!.loaded()) return;
|
65
|
+
triggeredMapReady = true;
|
54
66
|
if (onMapLoad) onMapLoad(map!, maplibre);
|
55
|
-
|
67
|
+
setTimeout(() => console.log('map_ready'), 100);
|
68
|
+
}
|
56
69
|
}
|
57
70
|
</script>
|
58
71
|
|
@@ -4,9 +4,10 @@ import maplibre from 'maplibre-gl';
|
|
4
4
|
import type { MapOptions } from 'maplibre-gl';
|
5
5
|
type $$ComponentProps = {
|
6
6
|
style?: string;
|
7
|
-
styleOptions?: Parameters<typeof getMapStyle>[
|
7
|
+
styleOptions?: Parameters<typeof getMapStyle>[0];
|
8
8
|
mapOptions?: Partial<MapOptions>;
|
9
9
|
map?: maplibre.Map;
|
10
|
+
emptyStyle?: boolean;
|
10
11
|
onMapInit?: (map: maplibre.Map, maplibre: typeof import('maplibre-gl')) => void;
|
11
12
|
onMapLoad?: (map: maplibre.Map, maplibre: typeof import('maplibre-gl')) => void;
|
12
13
|
};
|
@@ -1,31 +1,50 @@
|
|
1
1
|
<script lang="ts">
|
2
2
|
import type { Map as MaplibreMapType } from 'maplibre-gl';
|
3
3
|
import BasicMap from '../BasicMap/BasicMap.svelte';
|
4
|
-
import { onMount } from 'svelte';
|
5
4
|
import Sidebar from './components/Sidebar.svelte';
|
6
|
-
import
|
5
|
+
import { getCountryBoundingBox } from '../../utils/location.js';
|
6
|
+
import { GeometryManager } from './lib/geometry_manager.js';
|
7
|
+
import { GeometryManagerInteractive } from './lib/geometry_manager_interactive.js';
|
8
|
+
import { StateReader } from './lib/state/reader.js';
|
7
9
|
|
8
10
|
let showSidebar = $state(false);
|
11
|
+
let geometryManager: GeometryManager | GeometryManagerInteractive | undefined = $state();
|
9
12
|
|
10
|
-
|
11
|
-
|
12
|
-
if (!inIframe) showSidebar = true;
|
13
|
-
});
|
13
|
+
function onMapInit(map: MaplibreMapType, maplibre: typeof import('maplibre-gl')) {
|
14
|
+
showSidebar = window.self === window.top;
|
14
15
|
|
15
|
-
|
16
|
-
|
16
|
+
const padding = 10;
|
17
|
+
map.setPadding({
|
18
|
+
top: padding,
|
19
|
+
right: padding + (showSidebar ? 250 : 0),
|
20
|
+
bottom: padding,
|
21
|
+
left: padding
|
22
|
+
});
|
17
23
|
|
18
|
-
function onMapInit(_map: MaplibreMapType, maplibre: typeof import('maplibre-gl')) {
|
19
|
-
map = _map;
|
20
24
|
map.addControl(new maplibre.AttributionControl({ compact: true }), 'bottom-left');
|
21
25
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
if (showSidebar) {
|
27
|
+
geometryManager = new GeometryManagerInteractive(map);
|
28
|
+
} else {
|
29
|
+
geometryManager = new GeometryManager(map);
|
30
|
+
}
|
31
|
+
|
32
|
+
let hash = location.hash.slice(1);
|
33
|
+
if (!hash) hash = window.frameElement?.getAttribute('data') ?? '';
|
34
|
+
if (hash) {
|
35
|
+
readHash(hash);
|
36
|
+
} else {
|
37
|
+
const bbox = getCountryBoundingBox();
|
38
|
+
if (bbox) map.fitBounds(bbox, { animate: false });
|
39
|
+
}
|
40
|
+
|
41
|
+
addEventListener('hashchange', () => readHash(location.hash.slice(1)));
|
42
|
+
|
43
|
+
function readHash(hash: string) {
|
44
|
+
if (!geometryManager) return;
|
45
|
+
const stateReader = StateReader.fromBase64(hash);
|
46
|
+
geometryManager.loadState(stateReader.readRoot());
|
47
|
+
}
|
29
48
|
}
|
30
49
|
</script>
|
31
50
|
|
@@ -33,12 +52,13 @@
|
|
33
52
|
<div class="container">
|
34
53
|
<BasicMap
|
35
54
|
{onMapInit}
|
36
|
-
|
55
|
+
emptyStyle={true}
|
37
56
|
mapOptions={{ attributionControl: false }}
|
57
|
+
styleOptions={{ darkMode: false }}
|
38
58
|
></BasicMap>
|
39
59
|
</div>
|
40
|
-
{#if showSidebar && geometryManager}
|
41
|
-
<Sidebar {geometryManager}
|
60
|
+
{#if showSidebar && geometryManager && geometryManager.isInteractive()}
|
61
|
+
<Sidebar {geometryManager} />
|
42
62
|
|
43
63
|
<style>
|
44
64
|
.page .container {
|
@@ -52,6 +72,16 @@
|
|
52
72
|
</div>
|
53
73
|
|
54
74
|
<style>
|
75
|
+
.page {
|
76
|
+
--color-blue: #158;
|
77
|
+
--color-green: #1a1;
|
78
|
+
--color-bg: #fff;
|
79
|
+
--color-text: #000;
|
80
|
+
--btn-gap: 5px;
|
81
|
+
--gap: 10px;
|
82
|
+
--border-radius: 1em;
|
83
|
+
}
|
84
|
+
|
55
85
|
.page,
|
56
86
|
.container {
|
57
87
|
width: 100%;
|
@@ -59,6 +89,7 @@
|
|
59
89
|
position: relative;
|
60
90
|
min-height: 6em;
|
61
91
|
}
|
92
|
+
|
62
93
|
:global(.maplibregl-ctrl-attrib) {
|
63
94
|
display: flex;
|
64
95
|
align-items: center;
|
@@ -0,0 +1,92 @@
|
|
1
|
+
<script lang="ts">
|
2
|
+
import type { Snippet } from 'svelte';
|
3
|
+
import { EventHandler } from '../lib/utils/event_handler.js';
|
4
|
+
|
5
|
+
let {
|
6
|
+
children = $bindable(),
|
7
|
+
size,
|
8
|
+
onopen,
|
9
|
+
onclose
|
10
|
+
}: {
|
11
|
+
children?: Snippet;
|
12
|
+
size?: 'big' | 'fullscreen' | 'small';
|
13
|
+
onopen?: () => void;
|
14
|
+
onclose?: () => void;
|
15
|
+
} = $props();
|
16
|
+
|
17
|
+
let dialog: HTMLDialogElement | null = null;
|
18
|
+
export const eventHandler = new EventHandler();
|
19
|
+
|
20
|
+
export function getNode(): HTMLDialogElement {
|
21
|
+
return dialog!;
|
22
|
+
}
|
23
|
+
|
24
|
+
export function open() {
|
25
|
+
dialog?.showModal();
|
26
|
+
if (onopen) onopen();
|
27
|
+
eventHandler.emit('open');
|
28
|
+
}
|
29
|
+
|
30
|
+
export function close() {
|
31
|
+
dialog?.close();
|
32
|
+
if (onclose) onclose();
|
33
|
+
eventHandler.emit('close');
|
34
|
+
}
|
35
|
+
|
36
|
+
export function isOpen(): boolean {
|
37
|
+
return dialog?.open ?? false;
|
38
|
+
}
|
39
|
+
</script>
|
40
|
+
|
41
|
+
<dialog bind:this={dialog} class={size}>
|
42
|
+
<button onclick={close}>✕</button>
|
43
|
+
{@render children?.()}
|
44
|
+
</dialog>
|
45
|
+
|
46
|
+
<style>
|
47
|
+
dialog {
|
48
|
+
max-width: 100vw;
|
49
|
+
max-height: 100vh;
|
50
|
+
min-width: 300px;
|
51
|
+
min-height: 100px;
|
52
|
+
width: 80vw;
|
53
|
+
height: 80vh;
|
54
|
+
|
55
|
+
background-color: rgba(255, 255, 255, 0.8);
|
56
|
+
backdrop-filter: blur(10px);
|
57
|
+
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.2);
|
58
|
+
z-index: 10000;
|
59
|
+
border: 0.5px solid rgba(0, 0, 0, 0.3);
|
60
|
+
border-radius: 10px;
|
61
|
+
box-sizing: border-box;
|
62
|
+
padding: 20px;
|
63
|
+
|
64
|
+
&.fullscreen {
|
65
|
+
width: calc(100vw - 20px);
|
66
|
+
height: calc(100vh - 20px);
|
67
|
+
}
|
68
|
+
|
69
|
+
&.small {
|
70
|
+
width: fit-content;
|
71
|
+
height: fit-content;
|
72
|
+
}
|
73
|
+
}
|
74
|
+
|
75
|
+
dialog::backdrop {
|
76
|
+
backdrop-filter: blur(2px) brightness(0.9);
|
77
|
+
}
|
78
|
+
|
79
|
+
button {
|
80
|
+
position: absolute;
|
81
|
+
top: 5px;
|
82
|
+
right: 5px;
|
83
|
+
font-size: 20px;
|
84
|
+
cursor: pointer;
|
85
|
+
background: none;
|
86
|
+
border: none;
|
87
|
+
width: 25px;
|
88
|
+
height: 25px;
|
89
|
+
text-align: center;
|
90
|
+
padding: 0;
|
91
|
+
}
|
92
|
+
</style>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import type { Snippet } from 'svelte';
|
2
|
+
import { EventHandler } from '../lib/utils/event_handler.js';
|
3
|
+
type $$ComponentProps = {
|
4
|
+
children?: Snippet;
|
5
|
+
size?: 'big' | 'fullscreen' | 'small';
|
6
|
+
onopen?: () => void;
|
7
|
+
onclose?: () => void;
|
8
|
+
};
|
9
|
+
declare const Dialog: import("svelte").Component<$$ComponentProps, {
|
10
|
+
eventHandler: EventHandler;
|
11
|
+
getNode: () => HTMLDialogElement;
|
12
|
+
open: () => void;
|
13
|
+
close: () => void;
|
14
|
+
isOpen: () => boolean;
|
15
|
+
}, "children">;
|
16
|
+
type Dialog = ReturnType<typeof Dialog>;
|
17
|
+
export default Dialog;
|
@@ -0,0 +1,112 @@
|
|
1
|
+
<script lang="ts">
|
2
|
+
import { tick } from 'svelte';
|
3
|
+
import Dialog from './Dialog.svelte';
|
4
|
+
import { EventHandler } from '../lib/utils/event_handler.js';
|
5
|
+
|
6
|
+
type Mode = 'download' | 'new' | null;
|
7
|
+
let mode: Mode = $state(null);
|
8
|
+
let dialog: Dialog | null = null;
|
9
|
+
let input: HTMLInputElement | null = $state(null);
|
10
|
+
let eventHandler = new EventHandler();
|
11
|
+
|
12
|
+
async function openDialog(newMode: Mode) {
|
13
|
+
if (!dialog) return;
|
14
|
+
mode = newMode;
|
15
|
+
|
16
|
+
dialog.eventHandler.clear();
|
17
|
+
eventHandler.clear();
|
18
|
+
|
19
|
+
dialog.open();
|
20
|
+
await tick();
|
21
|
+
dialog.getNode()?.querySelector<HTMLButtonElement>('button[data-focus]')?.focus();
|
22
|
+
return;
|
23
|
+
}
|
24
|
+
|
25
|
+
async function closeDialog() {
|
26
|
+
dialog?.close();
|
27
|
+
dialog?.eventHandler.clear();
|
28
|
+
eventHandler.clear();
|
29
|
+
mode = null;
|
30
|
+
return tick();
|
31
|
+
}
|
32
|
+
|
33
|
+
export async function askDownloadFilename(initialFilename: string): Promise<string | null> {
|
34
|
+
if (!dialog) return null;
|
35
|
+
await openDialog('download');
|
36
|
+
initInput(initialFilename);
|
37
|
+
const { response, value } = await getResponse(true);
|
38
|
+
return (response && value?.trim()) || null;
|
39
|
+
}
|
40
|
+
|
41
|
+
export async function askCreateNew(): Promise<boolean> {
|
42
|
+
if (!dialog) return false;
|
43
|
+
await openDialog('new');
|
44
|
+
const { response } = await getResponse(false);
|
45
|
+
return response;
|
46
|
+
}
|
47
|
+
|
48
|
+
async function getResponse(defaultValue: boolean): Promise<{ response: boolean; value: string | null }> {
|
49
|
+
const response = await new Promise<boolean>((resolve) => {
|
50
|
+
if (!dialog) return resolve(false);
|
51
|
+
dialog!.eventHandler.on('close', () => resolve(false));
|
52
|
+
eventHandler.on('A', () => resolve(!defaultValue));
|
53
|
+
eventHandler.on('B', () => resolve(defaultValue));
|
54
|
+
});
|
55
|
+
const value = input?.value ?? null;
|
56
|
+
await closeDialog();
|
57
|
+
return { response, value };
|
58
|
+
}
|
59
|
+
|
60
|
+
function emitA() {
|
61
|
+
eventHandler.emit('A');
|
62
|
+
}
|
63
|
+
|
64
|
+
function emitB() {
|
65
|
+
eventHandler.emit('B');
|
66
|
+
}
|
67
|
+
|
68
|
+
function initInput(value: string) {
|
69
|
+
if (!input) return;
|
70
|
+
input.value = value;
|
71
|
+
input.addEventListener('keypress', (e) => {
|
72
|
+
if (e.key === 'Enter') eventHandler.emit('B');
|
73
|
+
});
|
74
|
+
}
|
75
|
+
</script>
|
76
|
+
|
77
|
+
<Dialog bind:this={dialog} size="small">
|
78
|
+
{#if mode == 'download'}
|
79
|
+
<h2>Download File</h2>
|
80
|
+
<label>
|
81
|
+
File name:
|
82
|
+
<input type="text" bind:this={input} spellcheck="false" />
|
83
|
+
</label>
|
84
|
+
<div class="grid2">
|
85
|
+
<button class="btn" onclick={emitA}>Cancel</button>
|
86
|
+
<button class="btn" onclick={emitB} data-focus>Download</button>
|
87
|
+
</div>
|
88
|
+
{/if}
|
89
|
+
{#if mode == 'new'}
|
90
|
+
<h2>New Map</h2>
|
91
|
+
<p>Do you want to create a new map?</p>
|
92
|
+
<div class="grid2">
|
93
|
+
<button class="btn" onclick={emitA}>OK</button>
|
94
|
+
<button class="btn" onclick={emitB} data-focus>Cancel</button>
|
95
|
+
</div>
|
96
|
+
{/if}
|
97
|
+
</Dialog>
|
98
|
+
|
99
|
+
<style>
|
100
|
+
h2 {
|
101
|
+
text-align: center;
|
102
|
+
margin-top: 0;
|
103
|
+
}
|
104
|
+
.grid2 {
|
105
|
+
margin: 0;
|
106
|
+
}
|
107
|
+
label,
|
108
|
+
p {
|
109
|
+
display: block;
|
110
|
+
margin-bottom: 10px;
|
111
|
+
}
|
112
|
+
</style>
|
@@ -0,0 +1,6 @@
|
|
1
|
+
declare const DialogFile: import("svelte").Component<Record<string, never>, {
|
2
|
+
askDownloadFilename: (initialFilename: string) => Promise<string | null>;
|
3
|
+
askCreateNew: () => Promise<boolean>;
|
4
|
+
}, "">;
|
5
|
+
type DialogFile = ReturnType<typeof DialogFile>;
|
6
|
+
export default DialogFile;
|