@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
@@ -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,33 +29,34 @@
|
|
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
|
|
35
36
|
<InputRow id="showStroke" label="Draw Outline">
|
36
|
-
<input
|
37
|
+
<input
|
38
|
+
id="showStroke"
|
39
|
+
type="checkbox"
|
40
|
+
bind:checked={
|
41
|
+
() => $strokeVisible,
|
42
|
+
(visible) => {
|
43
|
+
$strokeVisible = visible;
|
44
|
+
element.manager.state?.log();
|
45
|
+
}
|
46
|
+
}
|
47
|
+
/>
|
37
48
|
</InputRow>
|
38
49
|
|
39
50
|
{#if $strokeVisible}
|
40
51
|
<EditorStroke layer={element.strokeLayer} />
|
41
52
|
{/if}
|
42
53
|
{/if}
|
43
|
-
{#if element instanceof PolygonElement || element instanceof
|
54
|
+
{#if element instanceof LineElement || element instanceof PolygonElement || element instanceof CircleElement}
|
44
55
|
<hr />
|
45
|
-
<p>
|
56
|
+
<p class="label" style="margin: 0.5em 0 1em;">
|
46
57
|
Drag points to move.<br />Drag a midpoint to add.<br />Shift-click to delete a point.
|
47
58
|
</p>
|
48
59
|
{/if}
|
49
60
|
</div>
|
50
61
|
</SidebarPanel>
|
51
62
|
{/key}
|
52
|
-
|
53
|
-
<style>
|
54
|
-
.style-editor {
|
55
|
-
:global(p) {
|
56
|
-
opacity: 0.5;
|
57
|
-
margin: 0.5em 0 1em;
|
58
|
-
}
|
59
|
-
}
|
60
|
-
</style>
|
@@ -10,11 +10,30 @@
|
|
10
10
|
</script>
|
11
11
|
|
12
12
|
<InputRow label="Color" id="color">
|
13
|
-
<input
|
13
|
+
<input
|
14
|
+
id="color"
|
15
|
+
type="color"
|
16
|
+
bind:value={
|
17
|
+
() => $color,
|
18
|
+
(v) => {
|
19
|
+
$color = v;
|
20
|
+
layer.manager.state?.log();
|
21
|
+
}
|
22
|
+
}
|
23
|
+
/>
|
14
24
|
</InputRow>
|
15
25
|
|
16
26
|
<InputRow label="Pattern" id="pattern">
|
17
|
-
<select
|
27
|
+
<select
|
28
|
+
id="pattern"
|
29
|
+
bind:value={
|
30
|
+
() => $pattern,
|
31
|
+
(v) => {
|
32
|
+
$pattern = v;
|
33
|
+
layer.manager.state?.log();
|
34
|
+
}
|
35
|
+
}
|
36
|
+
>
|
18
37
|
{#each fillPatterns as [index, fill] (index)}
|
19
38
|
<option value={index}>{fill.name}</option>
|
20
39
|
{/each}
|
@@ -22,5 +41,18 @@
|
|
22
41
|
</InputRow>
|
23
42
|
|
24
43
|
<InputRow label="Opacity" id="opacity">
|
25
|
-
<input
|
44
|
+
<input
|
45
|
+
id="opacity"
|
46
|
+
type="range"
|
47
|
+
min="0"
|
48
|
+
max="1"
|
49
|
+
step="0.02"
|
50
|
+
bind:value={
|
51
|
+
() => $opacity,
|
52
|
+
(v) => {
|
53
|
+
$opacity = v;
|
54
|
+
layer.manager.state?.log();
|
55
|
+
}
|
56
|
+
}
|
57
|
+
/>
|
26
58
|
</InputRow>
|
@@ -10,11 +10,30 @@
|
|
10
10
|
</script>
|
11
11
|
|
12
12
|
<InputRow id="color" label="Color">
|
13
|
-
<input
|
13
|
+
<input
|
14
|
+
id="color"
|
15
|
+
type="color"
|
16
|
+
bind:value={
|
17
|
+
() => $color,
|
18
|
+
(v) => {
|
19
|
+
$color = v;
|
20
|
+
layer.manager.state?.log();
|
21
|
+
}
|
22
|
+
}
|
23
|
+
/>
|
14
24
|
</InputRow>
|
15
25
|
|
16
26
|
<InputRow id="dashed" label="Dashed">
|
17
|
-
<select
|
27
|
+
<select
|
28
|
+
id="dashed"
|
29
|
+
bind:value={
|
30
|
+
() => $dashed,
|
31
|
+
(v) => {
|
32
|
+
$dashed = v;
|
33
|
+
layer.manager.state?.log();
|
34
|
+
}
|
35
|
+
}
|
36
|
+
>
|
18
37
|
{#each dashArrays as [index, dash] (index)}
|
19
38
|
<option value={index}>{dash.name}</option>
|
20
39
|
{/each}
|
@@ -22,5 +41,18 @@
|
|
22
41
|
</InputRow>
|
23
42
|
|
24
43
|
<InputRow id="width" label="Width">
|
25
|
-
<input
|
44
|
+
<input
|
45
|
+
id="width"
|
46
|
+
type="range"
|
47
|
+
min="0.5"
|
48
|
+
max="5"
|
49
|
+
step="0.5"
|
50
|
+
bind:value={
|
51
|
+
() => $width,
|
52
|
+
(v) => {
|
53
|
+
$width = v;
|
54
|
+
layer.manager.state?.log();
|
55
|
+
}
|
56
|
+
}
|
57
|
+
/>
|
26
58
|
</InputRow>
|
@@ -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
|
|
@@ -15,31 +15,108 @@
|
|
15
15
|
</script>
|
16
16
|
|
17
17
|
<InputRow id="label" label="Symbol">
|
18
|
-
<SymbolSelector
|
18
|
+
<SymbolSelector
|
19
|
+
bind:symbolIndex={
|
20
|
+
() => $symbolIndex,
|
21
|
+
(v) => {
|
22
|
+
$symbolIndex = v;
|
23
|
+
layer.manager.state?.log();
|
24
|
+
}
|
25
|
+
}
|
26
|
+
map={layer.manager.map}
|
27
|
+
/>
|
19
28
|
</InputRow>
|
20
29
|
|
21
30
|
<InputRow id="color" label="Color">
|
22
|
-
<input
|
31
|
+
<input
|
32
|
+
id="color"
|
33
|
+
type="color"
|
34
|
+
bind:value={
|
35
|
+
() => $color,
|
36
|
+
(v) => {
|
37
|
+
$color = v;
|
38
|
+
layer.manager.state?.log();
|
39
|
+
}
|
40
|
+
}
|
41
|
+
/>
|
23
42
|
</InputRow>
|
24
43
|
|
25
44
|
<InputRow id="size" label="Size">
|
26
|
-
<input
|
45
|
+
<input
|
46
|
+
id="size"
|
47
|
+
type="range"
|
48
|
+
min="0.5"
|
49
|
+
max="3"
|
50
|
+
step="0.1"
|
51
|
+
bind:value={
|
52
|
+
() => $size,
|
53
|
+
(v) => {
|
54
|
+
$size = v;
|
55
|
+
layer.manager.state?.log();
|
56
|
+
}
|
57
|
+
}
|
58
|
+
/>
|
27
59
|
</InputRow>
|
28
60
|
|
29
61
|
<InputRow id="rotate" label="Rotation">
|
30
|
-
<input
|
62
|
+
<input
|
63
|
+
id="rotate"
|
64
|
+
type="range"
|
65
|
+
min="-180"
|
66
|
+
max="180"
|
67
|
+
step="15"
|
68
|
+
bind:value={
|
69
|
+
() => $rotate,
|
70
|
+
(v) => {
|
71
|
+
$rotate = v;
|
72
|
+
layer.manager.state?.log();
|
73
|
+
}
|
74
|
+
}
|
75
|
+
/>
|
31
76
|
</InputRow>
|
32
77
|
|
33
78
|
<InputRow id="halo" label="Halo">
|
34
|
-
<input
|
79
|
+
<input
|
80
|
+
id="halo"
|
81
|
+
type="range"
|
82
|
+
min="0"
|
83
|
+
max="3"
|
84
|
+
step="0.5"
|
85
|
+
bind:value={
|
86
|
+
() => $halo,
|
87
|
+
(v) => {
|
88
|
+
$halo = v;
|
89
|
+
layer.manager.state?.log();
|
90
|
+
}
|
91
|
+
}
|
92
|
+
/>
|
35
93
|
</InputRow>
|
36
94
|
|
37
95
|
<InputRow id="label" label="Label">
|
38
|
-
<input
|
96
|
+
<input
|
97
|
+
id="label"
|
98
|
+
type="label"
|
99
|
+
bind:value={
|
100
|
+
() => $label,
|
101
|
+
(v) => {
|
102
|
+
$label = v;
|
103
|
+
layer.manager.state?.log();
|
104
|
+
}
|
105
|
+
}
|
106
|
+
/>
|
39
107
|
</InputRow>
|
40
108
|
|
41
109
|
<InputRow id="labelAlign" label="Align Label">
|
42
|
-
<select
|
110
|
+
<select
|
111
|
+
id="labelAlign"
|
112
|
+
bind:value={
|
113
|
+
() => $labelAlign,
|
114
|
+
(v) => {
|
115
|
+
$labelAlign = v;
|
116
|
+
layer.manager.state?.log();
|
117
|
+
}
|
118
|
+
}
|
119
|
+
>
|
43
120
|
{#each labelPositions as { index, name } (index)}
|
44
121
|
<option value={index}>{name}</option>
|
45
122
|
{/each}
|
@@ -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>
|