@versatiles/svelte 2.0.1 → 2.1.1
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 +39 -19
- package/dist/components/BBoxMap/BBoxMap.svelte.d.ts +1 -1
- package/dist/components/BBoxMap/lib/bbox.d.ts +5 -2
- package/dist/components/BBoxMap/lib/bbox.js +33 -24
- 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 +6 -14
- package/dist/components/MapEditor/components/EditorFill.svelte +3 -3
- package/dist/components/MapEditor/components/EditorStroke.svelte +3 -3
- package/dist/components/MapEditor/components/EditorSymbol.svelte +9 -9
- 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 +51 -98
- package/dist/components/MapEditor/components/Sidebar.svelte.d.ts +3 -3
- package/dist/components/MapEditor/components/SidebarPanel.svelte +13 -5
- package/dist/components/MapEditor/lib/element/abstract.d.ts +8 -4
- package/dist/components/MapEditor/lib/element/abstract.js +10 -1
- 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 -160
- 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 -1
- package/dist/components/MapEditor/lib/map_layer/abstract.js +25 -22
- package/dist/components/MapEditor/lib/map_layer/fill.js +2 -4
- package/dist/components/MapEditor/lib/map_layer/line.js +1 -1
- package/dist/components/MapEditor/lib/map_layer/symbol.d.ts +2 -2
- 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 +7 -10
- package/dist/components/MapEditor/lib/state/manager.js +19 -54
- 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/index.d.ts +1 -0
- package/dist/index.js +1 -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
@@ -0,0 +1,216 @@
|
|
1
|
+
<script lang="ts">
|
2
|
+
import type { StateManager } from '../lib/state/manager.js';
|
3
|
+
import Dialog from './Dialog.svelte';
|
4
|
+
|
5
|
+
const { state: stateManager = $bindable() }: { state: StateManager } = $props();
|
6
|
+
|
7
|
+
let dialog: Dialog | undefined;
|
8
|
+
let iframe: HTMLIFrameElement | undefined;
|
9
|
+
let btnLink: HTMLButtonElement | undefined;
|
10
|
+
let btnEmbed: HTMLButtonElement | undefined;
|
11
|
+
let previewAspectRatio: 'wide' | 'square' | 'tall' = $state('wide');
|
12
|
+
|
13
|
+
const baseUrl = window.location.href.replace(/#.*$/, '');
|
14
|
+
|
15
|
+
let timeout: ReturnType<typeof setTimeout> | null = null;
|
16
|
+
let linkCode = $state('');
|
17
|
+
let embedCode = $state('');
|
18
|
+
|
19
|
+
export function open() {
|
20
|
+
dialog?.open();
|
21
|
+
update(1);
|
22
|
+
}
|
23
|
+
|
24
|
+
function getLinkCode() {
|
25
|
+
return `${baseUrl}#${stateManager.getHash()}`;
|
26
|
+
}
|
27
|
+
|
28
|
+
function getEmbedCode() {
|
29
|
+
return `<iframe src="${getLinkCode()}" style="width:100%; height:60vh" frameborder="0"></iframe>`;
|
30
|
+
}
|
31
|
+
|
32
|
+
function update(delay: number = 500) {
|
33
|
+
if (!dialog?.isOpen()) return;
|
34
|
+
linkCode = getLinkCode();
|
35
|
+
embedCode = getEmbedCode();
|
36
|
+
if (timeout != null) {
|
37
|
+
clearTimeout(timeout);
|
38
|
+
timeout = null;
|
39
|
+
}
|
40
|
+
timeout = setTimeout(() => {
|
41
|
+
if (!iframe) return;
|
42
|
+
if (iframe.src === linkCode) {
|
43
|
+
iframe.contentWindow?.location.reload();
|
44
|
+
} else {
|
45
|
+
iframe.src = getLinkCode();
|
46
|
+
}
|
47
|
+
}, delay);
|
48
|
+
}
|
49
|
+
|
50
|
+
export function close() {
|
51
|
+
dialog?.close();
|
52
|
+
}
|
53
|
+
|
54
|
+
function copyLink() {
|
55
|
+
navigator.clipboard.writeText(getLinkCode()).then(
|
56
|
+
() => flash(btnLink),
|
57
|
+
() => alert('Failed to copy link. Please try again.')
|
58
|
+
);
|
59
|
+
}
|
60
|
+
|
61
|
+
function copyEmbedCode() {
|
62
|
+
navigator.clipboard.writeText(getEmbedCode()).then(
|
63
|
+
() => flash(btnEmbed),
|
64
|
+
() => alert('Failed to copy embed code. Please try again.')
|
65
|
+
);
|
66
|
+
}
|
67
|
+
|
68
|
+
function flash(b?: HTMLButtonElement) {
|
69
|
+
if (!b) return;
|
70
|
+
b.classList.add('success');
|
71
|
+
setTimeout(() => b.classList.remove('success'), 2000);
|
72
|
+
}
|
73
|
+
|
74
|
+
function selectPreview(event: Event) {
|
75
|
+
const target = event.target as HTMLInputElement;
|
76
|
+
if (target.checked) {
|
77
|
+
previewAspectRatio = target.value as 'wide' | 'square' | 'tall';
|
78
|
+
setTimeout(() => iframe?.contentWindow?.location.reload(), 0);
|
79
|
+
}
|
80
|
+
}
|
81
|
+
</script>
|
82
|
+
|
83
|
+
<Dialog bind:this={dialog} size="fullscreen">
|
84
|
+
<div class="grid">
|
85
|
+
<div class="head">
|
86
|
+
<p>Share your map with others by copying the link or embed code below.</p>
|
87
|
+
</div>
|
88
|
+
<div class="left">
|
89
|
+
<iframe title="preview" bind:this={iframe} class={'aspect-' + previewAspectRatio}></iframe>
|
90
|
+
</div>
|
91
|
+
<div class="right">
|
92
|
+
<p>
|
93
|
+
<label for="text-link">
|
94
|
+
Link:
|
95
|
+
<textarea id="text-link" rows="3" readonly onclick={(e) => e.currentTarget.select()}>{linkCode}</textarea>
|
96
|
+
</label>
|
97
|
+
<button class="btn" bind:this={btnLink} onclick={copyLink}>Copy Link</button>
|
98
|
+
</p>
|
99
|
+
<p>
|
100
|
+
<label for="text-iframe">
|
101
|
+
Embed Code:
|
102
|
+
<textarea id="text-iframe" rows="5" readonly onclick={(e) => e.currentTarget.select()}>{embedCode}</textarea>
|
103
|
+
</label>
|
104
|
+
|
105
|
+
<button class="btn" bind:this={btnEmbed} onclick={copyEmbedCode}>Copy Embed Code</button>
|
106
|
+
</p>
|
107
|
+
</div>
|
108
|
+
<div class="bottom">
|
109
|
+
<button class="btn" onclick={() => update(0)}>Reload</button>
|
110
|
+
<fieldset class="btn">
|
111
|
+
<legend>Aspect ratio of the preview</legend>
|
112
|
+
<div>
|
113
|
+
<input type="radio" id="preview-wide" name="preview-ratio" value="wide" onclick={selectPreview} checked />
|
114
|
+
<label for="preview-wide">horizontal</label>
|
115
|
+
<input type="radio" id="preview-square" name="preview-ratio" value="square" onclick={selectPreview} />
|
116
|
+
<label for="preview-square">square</label>
|
117
|
+
<input type="radio" id="preview-tall" name="preview-ratio" value="tall" onclick={selectPreview} />
|
118
|
+
<label for="preview-tall">vertical</label>
|
119
|
+
</div>
|
120
|
+
</fieldset>
|
121
|
+
</div>
|
122
|
+
</div>
|
123
|
+
</Dialog>
|
124
|
+
|
125
|
+
<style>
|
126
|
+
.grid {
|
127
|
+
display: grid;
|
128
|
+
grid-template-columns: 1fr auto;
|
129
|
+
grid-template-rows: auto 1fr auto;
|
130
|
+
gap: 10px;
|
131
|
+
width: 100%;
|
132
|
+
height: 100%;
|
133
|
+
overflow: hidden;
|
134
|
+
|
135
|
+
.head {
|
136
|
+
grid-column: 1 / -1;
|
137
|
+
grid-row: 1 / 1;
|
138
|
+
text-align: center;
|
139
|
+
font-size: 1.2em;
|
140
|
+
margin-bottom: 20px;
|
141
|
+
}
|
142
|
+
|
143
|
+
.left {
|
144
|
+
grid-column: 1 / 2;
|
145
|
+
grid-row: 2 / 2;
|
146
|
+
display: flex;
|
147
|
+
justify-content: center;
|
148
|
+
align-items: center;
|
149
|
+
container-name: myContainer;
|
150
|
+
container-type: size;
|
151
|
+
|
152
|
+
iframe {
|
153
|
+
aspect-ratio: 16 / 9;
|
154
|
+
width: 100%;
|
155
|
+
height: auto;
|
156
|
+
border: 1px solid #000;
|
157
|
+
box-sizing: border-box;
|
158
|
+
background: #fff;
|
159
|
+
|
160
|
+
@container myContainer (min-aspect-ratio: 16 / 9) {
|
161
|
+
& {
|
162
|
+
width: auto;
|
163
|
+
height: 100%;
|
164
|
+
}
|
165
|
+
}
|
166
|
+
}
|
167
|
+
|
168
|
+
iframe.aspect-tall {
|
169
|
+
aspect-ratio: 9 / 16;
|
170
|
+
@container myContainer (min-aspect-ratio: 9 / 16) {
|
171
|
+
& {
|
172
|
+
width: auto;
|
173
|
+
height: 100%;
|
174
|
+
}
|
175
|
+
}
|
176
|
+
}
|
177
|
+
|
178
|
+
iframe.aspect-square {
|
179
|
+
aspect-ratio: 1/1;
|
180
|
+
@container myContainer (min-aspect-ratio: 1/1) {
|
181
|
+
& {
|
182
|
+
width: auto;
|
183
|
+
height: 100%;
|
184
|
+
}
|
185
|
+
}
|
186
|
+
}
|
187
|
+
}
|
188
|
+
|
189
|
+
.right {
|
190
|
+
grid-column: 2 / -1;
|
191
|
+
grid-row: 2 / -1;
|
192
|
+
text-align: left;
|
193
|
+
|
194
|
+
textarea {
|
195
|
+
width: 200px;
|
196
|
+
margin: 0 0 0.3rem;
|
197
|
+
display: block;
|
198
|
+
resize: none;
|
199
|
+
}
|
200
|
+
|
201
|
+
textarea[readonly] {
|
202
|
+
font-size: 0.6rem;
|
203
|
+
-webkit-user-select: all;
|
204
|
+
user-select: all;
|
205
|
+
color: color-mix(in srgb, var(--color-text) 60%, transparent);
|
206
|
+
}
|
207
|
+
}
|
208
|
+
|
209
|
+
.bottom {
|
210
|
+
grid-column: 1 / 1;
|
211
|
+
grid-row: 3 / 3;
|
212
|
+
text-align: center;
|
213
|
+
padding-top: 1rem;
|
214
|
+
}
|
215
|
+
}
|
216
|
+
</style>
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import type { StateManager } from '../lib/state/manager.js';
|
2
|
+
type $$ComponentProps = {
|
3
|
+
state: StateManager;
|
4
|
+
};
|
5
|
+
declare const DialogShare: import("svelte").Component<$$ComponentProps, {
|
6
|
+
open: () => void;
|
7
|
+
close: () => void;
|
8
|
+
}, "state">;
|
9
|
+
type DialogShare = ReturnType<typeof DialogShare>;
|
10
|
+
export default DialogShare;
|
@@ -9,12 +9,13 @@
|
|
9
9
|
import InputRow from './InputRow.svelte';
|
10
10
|
import SidebarPanel from './SidebarPanel.svelte';
|
11
11
|
import { writable } from 'svelte/store';
|
12
|
+
import { CircleElement } from '../lib/element/circle.js';
|
12
13
|
|
13
14
|
const { element }: { element: AbstractElement | undefined } = $props();
|
14
15
|
|
15
16
|
const noElement = $derived(!element);
|
16
17
|
const strokeVisible = $derived.by(() => {
|
17
|
-
if (element instanceof PolygonElement) return element.strokeLayer.visible;
|
18
|
+
if (element instanceof PolygonElement || element instanceof CircleElement) return element.strokeLayer.visible;
|
18
19
|
return writable(false);
|
19
20
|
});
|
20
21
|
</script>
|
@@ -28,7 +29,7 @@
|
|
28
29
|
{#if element instanceof LineElement}
|
29
30
|
<EditorStroke layer={element.layer} />
|
30
31
|
{/if}
|
31
|
-
{#if element instanceof PolygonElement}
|
32
|
+
{#if element instanceof PolygonElement || element instanceof CircleElement}
|
32
33
|
<EditorFill layer={element.fillLayer} />
|
33
34
|
<hr />
|
34
35
|
|
@@ -40,7 +41,7 @@
|
|
40
41
|
() => $strokeVisible,
|
41
42
|
(visible) => {
|
42
43
|
$strokeVisible = visible;
|
43
|
-
element.manager.state
|
44
|
+
element.manager.state?.log();
|
44
45
|
}
|
45
46
|
}
|
46
47
|
/>
|
@@ -50,21 +51,12 @@
|
|
50
51
|
<EditorStroke layer={element.strokeLayer} />
|
51
52
|
{/if}
|
52
53
|
{/if}
|
53
|
-
{#if element instanceof PolygonElement || element instanceof
|
54
|
+
{#if element instanceof LineElement || element instanceof PolygonElement || element instanceof CircleElement}
|
54
55
|
<hr />
|
55
|
-
<p>
|
56
|
+
<p class="label" style="margin: 0.5em 0 1em;">
|
56
57
|
Drag points to move.<br />Drag a midpoint to add.<br />Shift-click to delete a point.
|
57
58
|
</p>
|
58
59
|
{/if}
|
59
60
|
</div>
|
60
61
|
</SidebarPanel>
|
61
62
|
{/key}
|
62
|
-
|
63
|
-
<style>
|
64
|
-
.style-editor {
|
65
|
-
:global(p) {
|
66
|
-
opacity: 0.5;
|
67
|
-
margin: 0.5em 0 1em;
|
68
|
-
}
|
69
|
-
}
|
70
|
-
</style>
|
@@ -17,7 +17,7 @@
|
|
17
17
|
() => $color,
|
18
18
|
(v) => {
|
19
19
|
$color = v;
|
20
|
-
layer.manager.state
|
20
|
+
layer.manager.state?.log();
|
21
21
|
}
|
22
22
|
}
|
23
23
|
/>
|
@@ -30,7 +30,7 @@
|
|
30
30
|
() => $pattern,
|
31
31
|
(v) => {
|
32
32
|
$pattern = v;
|
33
|
-
layer.manager.state
|
33
|
+
layer.manager.state?.log();
|
34
34
|
}
|
35
35
|
}
|
36
36
|
>
|
@@ -51,7 +51,7 @@
|
|
51
51
|
() => $opacity,
|
52
52
|
(v) => {
|
53
53
|
$opacity = v;
|
54
|
-
layer.manager.state
|
54
|
+
layer.manager.state?.log();
|
55
55
|
}
|
56
56
|
}
|
57
57
|
/>
|
@@ -17,7 +17,7 @@
|
|
17
17
|
() => $color,
|
18
18
|
(v) => {
|
19
19
|
$color = v;
|
20
|
-
layer.manager.state
|
20
|
+
layer.manager.state?.log();
|
21
21
|
}
|
22
22
|
}
|
23
23
|
/>
|
@@ -30,7 +30,7 @@
|
|
30
30
|
() => $dashed,
|
31
31
|
(v) => {
|
32
32
|
$dashed = v;
|
33
|
-
layer.manager.state
|
33
|
+
layer.manager.state?.log();
|
34
34
|
}
|
35
35
|
}
|
36
36
|
>
|
@@ -51,7 +51,7 @@
|
|
51
51
|
() => $width,
|
52
52
|
(v) => {
|
53
53
|
$width = v;
|
54
|
-
layer.manager.state
|
54
|
+
layer.manager.state?.log();
|
55
55
|
}
|
56
56
|
}
|
57
57
|
/>
|
@@ -1,7 +1,7 @@
|
|
1
1
|
<script lang="ts">
|
2
2
|
import { labelPositions, MapLayerSymbol } from '../lib/map_layer/symbol.js';
|
3
3
|
import InputRow from './InputRow.svelte';
|
4
|
-
import SymbolSelector from './
|
4
|
+
import SymbolSelector from './PanelSymbolSelector.svelte';
|
5
5
|
|
6
6
|
const { layer }: { layer: MapLayerSymbol } = $props();
|
7
7
|
|
@@ -20,10 +20,10 @@
|
|
20
20
|
() => $symbolIndex,
|
21
21
|
(v) => {
|
22
22
|
$symbolIndex = v;
|
23
|
-
layer.manager.state
|
23
|
+
layer.manager.state?.log();
|
24
24
|
}
|
25
25
|
}
|
26
|
-
|
26
|
+
map={layer.manager.map}
|
27
27
|
/>
|
28
28
|
</InputRow>
|
29
29
|
|
@@ -35,7 +35,7 @@
|
|
35
35
|
() => $color,
|
36
36
|
(v) => {
|
37
37
|
$color = v;
|
38
|
-
layer.manager.state
|
38
|
+
layer.manager.state?.log();
|
39
39
|
}
|
40
40
|
}
|
41
41
|
/>
|
@@ -52,7 +52,7 @@
|
|
52
52
|
() => $size,
|
53
53
|
(v) => {
|
54
54
|
$size = v;
|
55
|
-
layer.manager.state
|
55
|
+
layer.manager.state?.log();
|
56
56
|
}
|
57
57
|
}
|
58
58
|
/>
|
@@ -69,7 +69,7 @@
|
|
69
69
|
() => $rotate,
|
70
70
|
(v) => {
|
71
71
|
$rotate = v;
|
72
|
-
layer.manager.state
|
72
|
+
layer.manager.state?.log();
|
73
73
|
}
|
74
74
|
}
|
75
75
|
/>
|
@@ -86,7 +86,7 @@
|
|
86
86
|
() => $halo,
|
87
87
|
(v) => {
|
88
88
|
$halo = v;
|
89
|
-
layer.manager.state
|
89
|
+
layer.manager.state?.log();
|
90
90
|
}
|
91
91
|
}
|
92
92
|
/>
|
@@ -100,7 +100,7 @@
|
|
100
100
|
() => $label,
|
101
101
|
(v) => {
|
102
102
|
$label = v;
|
103
|
-
layer.manager.state
|
103
|
+
layer.manager.state?.log();
|
104
104
|
}
|
105
105
|
}
|
106
106
|
/>
|
@@ -113,7 +113,7 @@
|
|
113
113
|
() => $labelAlign,
|
114
114
|
(v) => {
|
115
115
|
$labelAlign = v;
|
116
|
-
layer.manager.state
|
116
|
+
layer.manager.state?.log();
|
117
117
|
}
|
118
118
|
}
|
119
119
|
>
|
@@ -5,7 +5,7 @@
|
|
5
5
|
</script>
|
6
6
|
|
7
7
|
<div class="row">
|
8
|
-
<label for={id}>{label}</label>
|
8
|
+
<label class="label" for={id}>{label}</label>
|
9
9
|
{@render children()}
|
10
10
|
</div>
|
11
11
|
|
@@ -16,9 +16,8 @@
|
|
16
16
|
justify-content: space-between;
|
17
17
|
align-items: center;
|
18
18
|
color: var(--fg-color);
|
19
|
-
& >
|
19
|
+
& > label {
|
20
20
|
flex-grow: 0;
|
21
|
-
opacity: 0.5;
|
22
21
|
}
|
23
22
|
& > :global(button),
|
24
23
|
& > :global(input),
|
@@ -0,0 +1,73 @@
|
|
1
|
+
<script lang="ts">
|
2
|
+
import type { GeometryManagerInteractive } from '../lib/geometry_manager_interactive.js';
|
3
|
+
import Dialog from './DialogFile.svelte';
|
4
|
+
|
5
|
+
const { manager }: { manager: GeometryManagerInteractive } = $props();
|
6
|
+
|
7
|
+
const defaultFilename = 'default.mapjson';
|
8
|
+
let filename = defaultFilename;
|
9
|
+
const useFileAPI = false;
|
10
|
+
let dialog: Dialog | undefined = undefined;
|
11
|
+
const disabledSave = $state(false);
|
12
|
+
|
13
|
+
async function newFile(): Promise<void> {
|
14
|
+
if (!(await dialog?.askCreateNew())) return;
|
15
|
+
manager.clear();
|
16
|
+
filename = defaultFilename;
|
17
|
+
}
|
18
|
+
|
19
|
+
async function openFile(): Promise<void> {
|
20
|
+
if (!dialog) return;
|
21
|
+
|
22
|
+
const fileInput = document.createElement('input');
|
23
|
+
fileInput.type = 'file';
|
24
|
+
fileInput.accept = '.mapjson';
|
25
|
+
fileInput.onchange = async (event: Event) => {
|
26
|
+
const target = event.target as HTMLInputElement;
|
27
|
+
if (!target.files || target.files.length === 0) return;
|
28
|
+
const file = target.files[0];
|
29
|
+
filename = file.name;
|
30
|
+
const reader = new FileReader();
|
31
|
+
reader.onload = () => {
|
32
|
+
manager.loadState(JSON.parse(reader.result as string));
|
33
|
+
};
|
34
|
+
reader.readAsText(file);
|
35
|
+
};
|
36
|
+
fileInput.click();
|
37
|
+
}
|
38
|
+
|
39
|
+
async function saveFile(): Promise<void> {}
|
40
|
+
|
41
|
+
async function downloadFile(): Promise<void> {
|
42
|
+
if (!dialog) return;
|
43
|
+
const response = await dialog.askDownloadFilename(filename);
|
44
|
+
if (!response) return;
|
45
|
+
filename = response;
|
46
|
+
|
47
|
+
const state = manager.getState();
|
48
|
+
const blob = new Blob([JSON.stringify(state)], { type: 'application/json' });
|
49
|
+
const url = URL.createObjectURL(blob);
|
50
|
+
const a = document.createElement('a');
|
51
|
+
a.setAttribute('href', url);
|
52
|
+
a.setAttribute('download', filename);
|
53
|
+
a.click();
|
54
|
+
URL.revokeObjectURL(url);
|
55
|
+
}
|
56
|
+
async function saveFileAs(): Promise<void> {}
|
57
|
+
</script>
|
58
|
+
|
59
|
+
<div class="grid2">
|
60
|
+
<button class="btn" onclick={newFile}>New</button>
|
61
|
+
<button class="btn" onclick={openFile}>Open…</button>
|
62
|
+
</div>
|
63
|
+
<Dialog bind:this={dialog} />
|
64
|
+
{#if useFileAPI}
|
65
|
+
<div class="grid2">
|
66
|
+
<button class="btn" onclick={saveFile} disabled={disabledSave}>Save</button>
|
67
|
+
<button class="btn" onclick={saveFileAs}>Save As…</button>
|
68
|
+
</div>
|
69
|
+
{:else}
|
70
|
+
<div class="grid1">
|
71
|
+
<button class="btn" onclick={downloadFile}>Download</button>
|
72
|
+
</div>
|
73
|
+
{/if}
|
@@ -0,0 +1,7 @@
|
|
1
|
+
import type { GeometryManagerInteractive } from '../lib/geometry_manager_interactive.js';
|
2
|
+
type $$ComponentProps = {
|
3
|
+
manager: GeometryManagerInteractive;
|
4
|
+
};
|
5
|
+
declare const PanelFile: import("svelte").Component<$$ComponentProps, {}, "">;
|
6
|
+
type PanelFile = ReturnType<typeof PanelFile>;
|
7
|
+
export default PanelFile;
|
@@ -0,0 +1,82 @@
|
|
1
|
+
<script lang="ts">
|
2
|
+
import type { Action } from 'svelte/action';
|
3
|
+
import type { Map as MaplibreMap } from 'maplibre-gl';
|
4
|
+
import { SymbolLibrary } from '../lib/symbols.js';
|
5
|
+
import Dialog from './Dialog.svelte';
|
6
|
+
|
7
|
+
let dialog: Dialog;
|
8
|
+
const buttonIconSize = 20;
|
9
|
+
const listItemSize = 48;
|
10
|
+
const listIconSize = 32;
|
11
|
+
const retina = window.devicePixelRatio || 1;
|
12
|
+
|
13
|
+
let { symbolIndex = $bindable(), map }: { symbolIndex: number; map: MaplibreMap } = $props();
|
14
|
+
|
15
|
+
const symbolLibrary = new SymbolLibrary(map);
|
16
|
+
|
17
|
+
const drawIconHalo: Action<HTMLCanvasElement, number> = (canvas, index) => symbolLibrary.drawSymbol(canvas, index, 3);
|
18
|
+
|
19
|
+
const drawIcon: Action<HTMLCanvasElement, number> = (canvas, index) => symbolLibrary.drawSymbol(canvas, index, 3);
|
20
|
+
</script>
|
21
|
+
|
22
|
+
<button onclick={() => dialog?.open()} style="text-align: left; white-space: nowrap; overflow: hidden; padding: 1px">
|
23
|
+
{#key symbolIndex}
|
24
|
+
<canvas
|
25
|
+
width={buttonIconSize * retina}
|
26
|
+
height={buttonIconSize * retina}
|
27
|
+
use:drawIcon={symbolIndex}
|
28
|
+
style="width:{buttonIconSize}px;height:{buttonIconSize}px;vertical-align:middle"
|
29
|
+
></canvas>
|
30
|
+
{/key}
|
31
|
+
{#if symbolIndex !== undefined}
|
32
|
+
{symbolLibrary.getSymbol(symbolIndex)?.name}
|
33
|
+
{:else}
|
34
|
+
Select Symbol
|
35
|
+
{/if}
|
36
|
+
</button>
|
37
|
+
|
38
|
+
<Dialog bind:this={dialog}>
|
39
|
+
<div class="list" style="--list-icon-size: {listIconSize}px; --list-item-size: {listItemSize}px">
|
40
|
+
{#each symbolLibrary.asList() as symbol (symbol.index)}
|
41
|
+
<button class="item" onclick={() => (symbolIndex = symbol.index)}
|
42
|
+
><canvas width={listIconSize * retina} height={listIconSize * retina} use:drawIconHalo={symbol.index}
|
43
|
+
></canvas><br />{symbol.name}</button
|
44
|
+
>
|
45
|
+
{/each}
|
46
|
+
</div>
|
47
|
+
</Dialog>
|
48
|
+
|
49
|
+
<style>
|
50
|
+
.list {
|
51
|
+
width: 100%;
|
52
|
+
height: 100%;
|
53
|
+
overflow-y: scroll;
|
54
|
+
display: grid;
|
55
|
+
grid-template-columns: repeat(auto-fill, minmax(64px, 1fr));
|
56
|
+
row-gap: 10px;
|
57
|
+
column-gap: 0px;
|
58
|
+
justify-items: center;
|
59
|
+
|
60
|
+
.item {
|
61
|
+
width: var(--list-item-size);
|
62
|
+
height: var(--list-item-size);
|
63
|
+
cursor: pointer;
|
64
|
+
border: none;
|
65
|
+
font-size: 10px;
|
66
|
+
background: none;
|
67
|
+
line-height: 1em;
|
68
|
+
text-align: center;
|
69
|
+
padding: 0;
|
70
|
+
|
71
|
+
&:hover {
|
72
|
+
background-color: rgba(0, 0, 0, 0.1);
|
73
|
+
}
|
74
|
+
|
75
|
+
canvas {
|
76
|
+
display: inline-block;
|
77
|
+
width: var(--list-icon-size);
|
78
|
+
height: var(--list-icon-size);
|
79
|
+
}
|
80
|
+
}
|
81
|
+
}
|
82
|
+
</style>
|
@@ -0,0 +1,8 @@
|
|
1
|
+
import type { Map as MaplibreMap } from 'maplibre-gl';
|
2
|
+
type $$ComponentProps = {
|
3
|
+
symbolIndex: number;
|
4
|
+
map: MaplibreMap;
|
5
|
+
};
|
6
|
+
declare const PanelSymbolSelector: import("svelte").Component<$$ComponentProps, {}, "symbolIndex">;
|
7
|
+
type PanelSymbolSelector = ReturnType<typeof PanelSymbolSelector>;
|
8
|
+
export default PanelSymbolSelector;
|