@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.
Files changed (83) hide show
  1. package/dist/components/BBoxMap/BBoxMap.svelte +23 -12
  2. package/dist/components/BBoxMap/lib/bbox.d.ts +1 -1
  3. package/dist/components/BBoxMap/lib/bbox.js +19 -17
  4. package/dist/components/BasicMap/BasicMap.svelte +22 -9
  5. package/dist/components/BasicMap/BasicMap.svelte.d.ts +2 -1
  6. package/dist/components/MapEditor/MapEditor.svelte +51 -20
  7. package/dist/components/MapEditor/components/Dialog.svelte +92 -0
  8. package/dist/components/MapEditor/components/Dialog.svelte.d.ts +17 -0
  9. package/dist/components/MapEditor/components/DialogFile.svelte +112 -0
  10. package/dist/components/MapEditor/components/DialogFile.svelte.d.ts +6 -0
  11. package/dist/components/MapEditor/components/DialogShare.svelte +216 -0
  12. package/dist/components/MapEditor/components/DialogShare.svelte.d.ts +10 -0
  13. package/dist/components/MapEditor/components/Editor.svelte +16 -14
  14. package/dist/components/MapEditor/components/EditorFill.svelte +35 -3
  15. package/dist/components/MapEditor/components/EditorStroke.svelte +35 -3
  16. package/dist/components/MapEditor/components/EditorSymbol.svelte +85 -8
  17. package/dist/components/MapEditor/components/InputRow.svelte +2 -3
  18. package/dist/components/MapEditor/components/PanelFile.svelte +73 -0
  19. package/dist/components/MapEditor/components/PanelFile.svelte.d.ts +7 -0
  20. package/dist/components/MapEditor/components/PanelSymbolSelector.svelte +82 -0
  21. package/dist/components/MapEditor/components/PanelSymbolSelector.svelte.d.ts +8 -0
  22. package/dist/components/MapEditor/components/Sidebar.svelte +59 -103
  23. package/dist/components/MapEditor/components/Sidebar.svelte.d.ts +3 -3
  24. package/dist/components/MapEditor/components/SidebarPanel.svelte +26 -9
  25. package/dist/components/MapEditor/lib/element/abstract.d.ts +8 -4
  26. package/dist/components/MapEditor/lib/element/abstract.js +11 -2
  27. package/dist/components/MapEditor/lib/element/abstract_path.d.ts +3 -2
  28. package/dist/components/MapEditor/lib/element/abstract_path.js +6 -3
  29. package/dist/components/MapEditor/lib/element/circle.d.ts +25 -0
  30. package/dist/components/MapEditor/lib/element/circle.js +118 -0
  31. package/dist/components/MapEditor/lib/element/line.d.ts +2 -2
  32. package/dist/components/MapEditor/lib/element/line.js +1 -1
  33. package/dist/components/MapEditor/lib/element/marker.d.ts +4 -3
  34. package/dist/components/MapEditor/lib/element/marker.js +2 -2
  35. package/dist/components/MapEditor/lib/element/polygon.d.ts +2 -2
  36. package/dist/components/MapEditor/lib/element/polygon.js +2 -2
  37. package/dist/components/MapEditor/lib/element/types.d.ts +2 -3
  38. package/dist/components/MapEditor/lib/geometry_manager.d.ts +12 -29
  39. package/dist/components/MapEditor/lib/geometry_manager.js +44 -162
  40. package/dist/components/MapEditor/lib/geometry_manager_interactive.d.ts +33 -0
  41. package/dist/components/MapEditor/lib/geometry_manager_interactive.js +102 -0
  42. package/dist/components/MapEditor/lib/map_layer/abstract.d.ts +2 -2
  43. package/dist/components/MapEditor/lib/map_layer/abstract.js +25 -25
  44. package/dist/components/MapEditor/lib/map_layer/fill.js +5 -16
  45. package/dist/components/MapEditor/lib/map_layer/line.js +5 -17
  46. package/dist/components/MapEditor/lib/map_layer/symbol.js +1 -1
  47. package/dist/components/MapEditor/lib/selection.d.ts +11 -0
  48. package/dist/components/MapEditor/lib/selection.js +70 -0
  49. package/dist/components/MapEditor/lib/state/constants.js +5 -6
  50. package/dist/components/MapEditor/lib/state/history.d.ts +14 -0
  51. package/dist/components/MapEditor/lib/state/history.js +53 -0
  52. package/dist/components/MapEditor/lib/state/manager.d.ts +8 -10
  53. package/dist/components/MapEditor/lib/state/manager.js +24 -48
  54. package/dist/components/MapEditor/lib/state/reader.d.ts +6 -4
  55. package/dist/components/MapEditor/lib/state/reader.js +70 -18
  56. package/dist/components/MapEditor/lib/state/types.d.ts +19 -2
  57. package/dist/components/MapEditor/lib/state/utils.d.ts +2 -0
  58. package/dist/components/MapEditor/lib/state/utils.js +12 -0
  59. package/dist/components/MapEditor/lib/state/writer.d.ts +6 -4
  60. package/dist/components/MapEditor/lib/state/writer.js +59 -19
  61. package/dist/components/MapEditor/lib/symbols.d.ts +1 -1
  62. package/dist/components/MapEditor/lib/symbols.js +47 -28
  63. package/dist/components/MapEditor/lib/utils/event_handler.d.ts +10 -0
  64. package/dist/components/MapEditor/lib/utils/event_handler.js +39 -0
  65. package/dist/components/MapEditor/lib/utils/geometry.d.ts +12 -0
  66. package/dist/components/MapEditor/lib/utils/geometry.js +87 -0
  67. package/dist/components/MapEditor/lib/utils/types.d.ts +2 -0
  68. package/dist/components/MapEditor/lib/utils/types.js +1 -0
  69. package/dist/components/MapEditor/style/button.scss +115 -0
  70. package/dist/components/MapEditor/style/index.scss +3 -0
  71. package/dist/components/MapEditor/style/layout.scss +20 -0
  72. package/dist/components/MapEditor/style/other.scss +10 -0
  73. package/dist/utils/location.d.ts +1 -0
  74. package/dist/utils/location.js +181 -0
  75. package/dist/utils/map_style.d.ts +2 -2
  76. package/dist/utils/map_style.js +2 -2
  77. package/package.json +29 -29
  78. package/dist/components/MapEditor/components/SymbolSelector.svelte +0 -110
  79. package/dist/components/MapEditor/components/SymbolSelector.svelte.d.ts +0 -8
  80. package/dist/components/MapEditor/lib/utils.d.ts +0 -6
  81. package/dist/components/MapEditor/lib/utils.js +0 -23
  82. /package/dist/components/MapEditor/lib/{geocoder.d.ts → utils/geocoder.d.ts} +0 -0
  83. /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 id="showStroke" type="checkbox" bind:checked={$strokeVisible} />
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 LineElement}
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 id="color" type="color" bind:value={$color} />
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 id="pattern" bind:value={$pattern}>
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 id="opacity" type="range" min="0" max="1" step="0.02" bind:value={$opacity} />
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 id="color" type="color" bind:value={$color} />
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 id="dashed" bind:value={$dashed}>
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 id="width" type="range" min="0.5" max="5" step="0.5" bind:value={$width} />
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 './SymbolSelector.svelte';
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 bind:symbolIndex={$symbolIndex} symbolLibrary={layer.manager.symbolLibrary} />
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 id="color" type="color" bind:value={$color} />
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 id="size" type="range" min="0.5" max="3" step="0.1" bind:value={$size} />
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 id="rotate" type="range" min="-180" max="180" step="15" bind:value={$rotate} />
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 id="halo" type="range" min="0" max="3" step="0.5" bind:value={$halo} />
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 id="label" type="label" bind:value={$label} />
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 id="labelAlign" bind:value={$labelAlign}>
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
- & > :global(label) {
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>