@tileflow/svelte 0.1.0-alpha.19 → 0.1.0-alpha.20
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/GENERATED_OUTPUT_LICENSE.md +21 -0
- package/LICENSE +201 -0
- package/NOTICE +9 -0
- package/README.md +151 -6
- package/TRADEMARKS.md +10 -0
- package/package.json +15 -9
- package/src/TileflowMap.svelte +1062 -122
- package/src/index.d.ts +91 -20
- package/src/maplibre.js +18 -0
- package/src/style-source.d.ts +17 -0
- package/src/style-source.js +35 -0
- package/src/TileflowMap.svelte.d.ts +0 -8
package/src/TileflowMap.svelte
CHANGED
|
@@ -1,155 +1,357 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
import type {
|
|
3
|
+
LngLatLike,
|
|
4
|
+
Map as MapLibreMap,
|
|
5
|
+
Marker as MapLibreMarker,
|
|
6
|
+
PointLike,
|
|
7
|
+
Popup as MapLibrePopup,
|
|
8
|
+
StyleSpecification,
|
|
7
9
|
} from 'maplibre-gl';
|
|
8
10
|
import {createEventDispatcher, onMount, tick} from 'svelte';
|
|
11
|
+
import {normalizeTileflowCaptureId} from '@tileflow/core/capture';
|
|
9
12
|
import {
|
|
10
13
|
createTileflowSessionController,
|
|
11
14
|
defaultTileflowManifestUrl,
|
|
15
|
+
defaultTileflowRuntimeView,
|
|
12
16
|
loadTileflowManifest,
|
|
13
17
|
mergeTileflowAnalytics,
|
|
14
|
-
|
|
18
|
+
normalizeTileflowRuntimeCenter,
|
|
15
19
|
normalizeTileflowStaticImageSize,
|
|
16
20
|
resolveTileflowManifestMap,
|
|
17
21
|
resolveTileflowMapMode,
|
|
18
22
|
resolveTileflowRuntimeStyle,
|
|
23
|
+
resolveTileflowRuntimeTheme,
|
|
24
|
+
resolveTileflowRuntimeView,
|
|
19
25
|
resolveTileflowStaticImageUrl,
|
|
20
26
|
shouldLoadTileflowManifest,
|
|
21
|
-
type MapLibreStyle,
|
|
22
|
-
type TileflowAnalytics,
|
|
23
|
-
type TileflowConfig,
|
|
24
27
|
type TileflowMapMarker,
|
|
25
|
-
type TileflowProjectThemes,
|
|
26
28
|
type TileflowRuntimeManifestMap,
|
|
27
|
-
} from '@tileflow/core';
|
|
29
|
+
} from '@tileflow/core/runtime';
|
|
28
30
|
import {
|
|
31
|
+
attachTileflowFairUseNotice,
|
|
29
32
|
attachTileflowMapLifecycle,
|
|
30
|
-
createTileflowMarkerController,
|
|
31
33
|
createTileflowSessionStarter,
|
|
34
|
+
createTileflowThemeController,
|
|
32
35
|
createTileflowTransformRequest,
|
|
36
|
+
getTileflowSystemColorScheme,
|
|
37
|
+
loadTileflowStyleFonts,
|
|
38
|
+
registerTileflowContourProtocol,
|
|
39
|
+
registerTileflowWorldRequestBridge,
|
|
40
|
+
subscribeTileflowSystemColorScheme,
|
|
41
|
+
type TileflowFairUseNoticeController,
|
|
33
42
|
type TileflowMapLifecycleAttachment,
|
|
43
|
+
type TileflowThemeController,
|
|
44
|
+
type TileflowWorldRequestBridge,
|
|
34
45
|
} from '@tileflow/core/browser';
|
|
46
|
+
import {
|
|
47
|
+
initialTileflowInteractionState,
|
|
48
|
+
tileflowInteractionStateSchema,
|
|
49
|
+
validateTileflowAnnotations,
|
|
50
|
+
validateTileflowInteractionBindings,
|
|
51
|
+
type TileflowAnnotation,
|
|
52
|
+
type TileflowAnnotationViewContext,
|
|
53
|
+
type TileflowInteractionBinding,
|
|
54
|
+
type TileflowInteractionContent,
|
|
55
|
+
type TileflowInteractionDiagnostic,
|
|
56
|
+
type TileflowInteractionDiagnosticCode,
|
|
57
|
+
type TileflowInteractionState,
|
|
58
|
+
type TileflowInteractionViewContext,
|
|
59
|
+
} from '@tileflow/interactions';
|
|
60
|
+
import {
|
|
61
|
+
createTileflowMapLibreDomRuntime,
|
|
62
|
+
createTileflowMapLibreInteractionCoordinator,
|
|
63
|
+
createTileflowMapLibreSemanticDomRuntime,
|
|
64
|
+
normalizeTileflowLegacyMarkers,
|
|
65
|
+
type TileflowMapLibreDomRenderTarget,
|
|
66
|
+
type TileflowMapLibreDomRuntime,
|
|
67
|
+
type TileflowMapLibreInteractionCoordinator,
|
|
68
|
+
type TileflowMapLibrePoiMap,
|
|
69
|
+
type TileflowMapLibreSemanticDomRenderTarget,
|
|
70
|
+
type TileflowMapLibreSemanticDomRuntime,
|
|
71
|
+
} from '@tileflow/interactions/maplibre';
|
|
72
|
+
import type {TileflowMapProps} from './index.js';
|
|
73
|
+
import {loadTileflowMapLibre} from './maplibre.js';
|
|
74
|
+
import {assertTileflowMapStyleInputs} from './style-source.js';
|
|
75
|
+
|
|
76
|
+
export let alt: NonNullable<TileflowMapProps['alt']> = '';
|
|
77
|
+
export let analytics: TileflowMapProps['analytics'] = undefined;
|
|
78
|
+
export let annotations: TileflowMapProps['annotations'] = undefined;
|
|
35
79
|
|
|
36
|
-
export let
|
|
37
|
-
export let
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
export let
|
|
44
|
-
export let
|
|
45
|
-
export let
|
|
46
|
-
export let
|
|
47
|
-
export let
|
|
48
|
-
export let
|
|
49
|
-
export let
|
|
50
|
-
export let
|
|
51
|
-
export let
|
|
52
|
-
export let
|
|
53
|
-
export let
|
|
54
|
-
export let
|
|
55
|
-
export let
|
|
56
|
-
export let
|
|
57
|
-
export let
|
|
58
|
-
export let
|
|
59
|
-
export let styleUrl: string | undefined = undefined;
|
|
60
|
-
export let themes: TileflowProjectThemes | undefined = undefined;
|
|
61
|
-
export let zoom: number | undefined = undefined;
|
|
80
|
+
export let center: TileflowMapProps['center'] = undefined;
|
|
81
|
+
export let captureId: TileflowMapProps['captureId'] = undefined;
|
|
82
|
+
export let className: NonNullable<TileflowMapProps['className']> = '';
|
|
83
|
+
export let defaultInteractionState: TileflowMapProps['defaultInteractionState'] = undefined;
|
|
84
|
+
export let height: NonNullable<TileflowMapProps['height']> = 420;
|
|
85
|
+
export let imageLoading: NonNullable<TileflowMapProps['imageLoading']> = 'eager';
|
|
86
|
+
export let imageUrl: TileflowMapProps['imageUrl'] = undefined;
|
|
87
|
+
export let interactive: TileflowMapProps['interactive'] = undefined;
|
|
88
|
+
export let interactions: TileflowMapProps['interactions'] = undefined;
|
|
89
|
+
export let interactionState: TileflowMapProps['interactionState'] = undefined;
|
|
90
|
+
export let mapOptions: TileflowMapProps['mapOptions'] = undefined;
|
|
91
|
+
export let marker: TileflowMapProps['marker'] = undefined;
|
|
92
|
+
export let markers: TileflowMapProps['markers'] = undefined;
|
|
93
|
+
export let mode: NonNullable<TileflowMapProps['mode']> = 'interactive';
|
|
94
|
+
export let onInteractionDiagnostic: TileflowMapProps['onInteractionDiagnostic'] = undefined;
|
|
95
|
+
export let onInteractionEvent: TileflowMapProps['onInteractionEvent'] = undefined;
|
|
96
|
+
export let onInteractionStateChange: TileflowMapProps['onInteractionStateChange'] = undefined;
|
|
97
|
+
export let onThemeChange: TileflowMapProps['onThemeChange'] = undefined;
|
|
98
|
+
export let popup: TileflowMapProps['popup'] = undefined;
|
|
99
|
+
export let source: TileflowMapProps['source'];
|
|
100
|
+
export let theme: TileflowMapProps['theme'] = undefined;
|
|
101
|
+
export let tooltip: TileflowMapProps['tooltip'] = undefined;
|
|
102
|
+
export let zoom: TileflowMapProps['zoom'] = undefined;
|
|
62
103
|
|
|
63
104
|
let container: HTMLDivElement;
|
|
64
|
-
let
|
|
105
|
+
let mapCaptureState: 'error' | 'idle' | 'loading' = 'loading';
|
|
65
106
|
let imageSize: {height: number; width: number} | null = null;
|
|
66
107
|
let imageResizeObserver: ResizeObserver | null = null;
|
|
67
108
|
let mapInstance: MapLibreMap | null = null;
|
|
109
|
+
let activeRuntimeResource: unknown = undefined;
|
|
110
|
+
let mapFairUseNotice: TileflowFairUseNoticeController | null = null;
|
|
68
111
|
let mapLifecycleAttachment: TileflowMapLifecycleAttachment | null = null;
|
|
112
|
+
let annotationRuntime: TileflowMapLibreDomRuntime<TileflowAnnotation> | null = null;
|
|
113
|
+
let annotationRuntimeDiagnosticsUnsubscribe: (() => void) | null = null;
|
|
114
|
+
let annotationRuntimeEventsUnsubscribe: (() => void) | null = null;
|
|
115
|
+
let annotationRuntimeTargetsUnsubscribe: (() => void) | null = null;
|
|
116
|
+
let annotationRenderTargets: readonly TileflowMapLibreDomRenderTarget<TileflowAnnotation>[] = [];
|
|
117
|
+
let annotationRuntimeCoordinatorDetach: (() => void) | null = null;
|
|
118
|
+
let annotationRuntimeDiagnostics: readonly TileflowInteractionDiagnostic[] = [];
|
|
119
|
+
let semanticRuntime: TileflowMapLibreSemanticDomRuntime | null = null;
|
|
120
|
+
let semanticRuntimeCoordinatorDetach: (() => void) | null = null;
|
|
121
|
+
let semanticRuntimeDiagnosticsUnsubscribe: (() => void) | null = null;
|
|
122
|
+
let semanticRuntimeEventsUnsubscribe: (() => void) | null = null;
|
|
123
|
+
let semanticRuntimeTargetsUnsubscribe: (() => void) | null = null;
|
|
124
|
+
let semanticRenderTargets: readonly TileflowMapLibreSemanticDomRenderTarget[] = [];
|
|
125
|
+
let semanticRuntimeDiagnostics: readonly TileflowInteractionDiagnostic[] = [];
|
|
126
|
+
let initializeSemanticRuntime: (() => void) | null = null;
|
|
127
|
+
let bridgeInteractionDiagnostics: Partial<
|
|
128
|
+
Record<BridgeInteractionDiagnosticSource, TileflowInteractionDiagnostic>
|
|
129
|
+
> = {};
|
|
130
|
+
let activeInteractionDiagnosticKeys = new Set<string>();
|
|
131
|
+
let interactionCaptureState: 'idle' | 'loading' = 'idle';
|
|
132
|
+
let themeTransitionState: 'error' | 'idle' | 'loading' = 'idle';
|
|
133
|
+
let effectiveInteractionCaptureState: 'error' | 'idle' | 'loading' = 'idle';
|
|
134
|
+
let captureState: 'error' | 'idle' | 'loading' = 'loading';
|
|
135
|
+
let hasInteractionErrors = false;
|
|
136
|
+
let interactionReadinessRunId = 0;
|
|
137
|
+
let interactionReadinessTargetKeys: readonly string[] = [];
|
|
138
|
+
let hadInteractionErrors = false;
|
|
139
|
+
let interactionRuntimesDisposing = false;
|
|
140
|
+
let legacyTitles: ReadonlyMap<string, string> = new Map();
|
|
69
141
|
let mapResizeObserver: ResizeObserver | null = null;
|
|
142
|
+
let mapWorldRequestBridge: TileflowWorldRequestBridge | null = null;
|
|
143
|
+
let themeController: TileflowThemeController | null = null;
|
|
144
|
+
let systemColorScheme: 'dark' | 'light' = 'light';
|
|
145
|
+
let unsubscribeSystemColorScheme: (() => void) | null = null;
|
|
146
|
+
let loadedManifestMap: TileflowRuntimeManifestMap | null = null;
|
|
70
147
|
let manifestMap: TileflowRuntimeManifestMap | null = null;
|
|
71
148
|
let manifestLoadId = 0;
|
|
149
|
+
let manifestResolutionKey = '';
|
|
150
|
+
let manifestResolutionState: 'error' | 'loading' | 'not-needed' | 'ready' = 'loading';
|
|
151
|
+
let mapLoadId = 0;
|
|
72
152
|
let mounted = false;
|
|
73
153
|
let refreshRunId = 0;
|
|
74
154
|
let readinessRunId = 0;
|
|
75
155
|
|
|
76
156
|
const dispatch = createEventDispatcher<{load: MapLibreMap}>();
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
$:
|
|
157
|
+
const controlledInteractionOwnership = interactionState !== undefined;
|
|
158
|
+
const initialInteractionState =
|
|
159
|
+
validateInteractionStateInput(interactionState, defaultInteractionState).length === 0
|
|
160
|
+
? (interactionState ?? defaultInteractionState ?? initialTileflowInteractionState)
|
|
161
|
+
: initialTileflowInteractionState;
|
|
162
|
+
const interactionCoordinator: TileflowMapLibreInteractionCoordinator =
|
|
163
|
+
createTileflowMapLibreInteractionCoordinator({
|
|
164
|
+
onInteractionStateChange(nextState) {
|
|
165
|
+
if (interactionRuntimesDisposing) return;
|
|
166
|
+
onInteractionStateChange?.(nextState);
|
|
167
|
+
},
|
|
168
|
+
...(controlledInteractionOwnership
|
|
169
|
+
? {interactionState: initialInteractionState}
|
|
170
|
+
: {defaultInteractionState: initialInteractionState}),
|
|
171
|
+
});
|
|
172
|
+
$: resolvedMode = resolveTileflowMapMode({mode});
|
|
173
|
+
$: assertTileflowMapStyleInputs({source, theme});
|
|
94
174
|
$: resolvedCaptureId = normalizeTileflowCaptureId(captureId);
|
|
95
175
|
$: isImageMode = resolvedMode === 'image';
|
|
96
|
-
$:
|
|
97
|
-
$:
|
|
98
|
-
|
|
99
|
-
|
|
176
|
+
$: mapName = source?.kind === 'tileflow' ? source.map : undefined;
|
|
177
|
+
$: manifestUrl =
|
|
178
|
+
source?.kind === 'tileflow'
|
|
179
|
+
? (source.manifestUrl ?? defaultTileflowManifestUrl)
|
|
180
|
+
: defaultTileflowManifestUrl;
|
|
181
|
+
$: shouldLoadManifest = shouldLoadTileflowManifest({
|
|
182
|
+
source,
|
|
183
|
+
});
|
|
184
|
+
$: manifestRequestKey = shouldLoadManifest
|
|
185
|
+
? JSON.stringify([manifestUrl, mapName])
|
|
186
|
+
: 'not-needed';
|
|
187
|
+
$: currentManifestResolutionState =
|
|
188
|
+
manifestResolutionKey === manifestRequestKey
|
|
189
|
+
? manifestResolutionState
|
|
190
|
+
: shouldLoadManifest
|
|
191
|
+
? 'loading'
|
|
192
|
+
: 'not-needed';
|
|
193
|
+
$: manifestMap =
|
|
194
|
+
manifestResolutionKey === manifestRequestKey && manifestResolutionState === 'ready'
|
|
195
|
+
? loadedManifestMap
|
|
196
|
+
: null;
|
|
197
|
+
$: themeResolution = resolveThemeSelection({
|
|
198
|
+
colorScheme: systemColorScheme,
|
|
199
|
+
manifestMap,
|
|
200
|
+
source,
|
|
201
|
+
theme,
|
|
202
|
+
});
|
|
203
|
+
$: resolvedThemeName = themeResolution.name;
|
|
204
|
+
$: manifestView = resolveTileflowRuntimeView({manifestMap});
|
|
205
|
+
$: manifestCenter = normalizeTileflowRuntimeCenter(manifestView.center);
|
|
206
|
+
$: imageCenter = normalizeTileflowRuntimeCenter(center ?? mapOptions?.center, manifestCenter);
|
|
207
|
+
$: imageZoom = zoom ?? mapOptions?.zoom ?? manifestView.zoom ?? defaultTileflowRuntimeView.zoom;
|
|
208
|
+
$: resolvedCenter = center ?? mapOptions?.center ?? manifestCenter;
|
|
209
|
+
$: resolvedZoom =
|
|
210
|
+
zoom ?? mapOptions?.zoom ?? manifestView.zoom ?? defaultTileflowRuntimeView.zoom;
|
|
211
|
+
$: resolvedBearing = mapOptions?.bearing ?? manifestView.bearing;
|
|
212
|
+
$: resolvedPitch = mapOptions?.pitch ?? manifestView.pitch;
|
|
100
213
|
$: resolvedInteractive = interactive ?? mapOptions?.interactive ?? true;
|
|
101
|
-
$: runtimeStyle =
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
styleUrl,
|
|
111
|
-
themes,
|
|
112
|
-
});
|
|
214
|
+
$: runtimeStyle =
|
|
215
|
+
isImageMode || themeResolution.error
|
|
216
|
+
? null
|
|
217
|
+
: resolveTileflowRuntimeStyle({
|
|
218
|
+
colorScheme: systemColorScheme,
|
|
219
|
+
manifestMap,
|
|
220
|
+
source,
|
|
221
|
+
theme: themeResolution.name,
|
|
222
|
+
});
|
|
113
223
|
$: resolvedAnalytics = mergeTileflowAnalytics(analytics, runtimeStyle?.analytics);
|
|
114
224
|
$: runtimeImageUrl =
|
|
115
225
|
imageUrl ??
|
|
116
|
-
(isImageMode
|
|
226
|
+
(isImageMode && !themeResolution.error
|
|
117
227
|
? resolveTileflowStaticImageUrl({
|
|
118
228
|
center: imageCenter,
|
|
229
|
+
colorScheme: systemColorScheme,
|
|
119
230
|
imageSize,
|
|
120
231
|
manifestMap,
|
|
232
|
+
theme: themeResolution.name,
|
|
121
233
|
zoom: imageZoom,
|
|
122
234
|
})
|
|
123
235
|
: undefined);
|
|
236
|
+
$: runtimeResolutionState = resolveRuntimeResolutionState({
|
|
237
|
+
currentManifestResolutionState,
|
|
238
|
+
imageSize,
|
|
239
|
+
imageUrl,
|
|
240
|
+
isImageMode,
|
|
241
|
+
runtimeImageUrl,
|
|
242
|
+
runtimeStyle,
|
|
243
|
+
shouldLoadManifest,
|
|
244
|
+
themeResolutionError: themeResolution.error,
|
|
245
|
+
});
|
|
246
|
+
$: currentMapCaptureState =
|
|
247
|
+
activeRuntimeResource === (isImageMode ? runtimeImageUrl : runtimeStyle)
|
|
248
|
+
? mapCaptureState
|
|
249
|
+
: 'loading';
|
|
124
250
|
$: frameStyle = `height: ${formatHeight(height)}; min-height: 240px; overflow: hidden; position: relative; width: 100%;`;
|
|
251
|
+
$: annotationResolution = resolveAnnotationInput(annotations, markers);
|
|
252
|
+
$: interactionBindingResolution = resolveInteractionBindingInput(interactions);
|
|
253
|
+
$: interactionStateDiagnostics = validateInteractionStateInput(
|
|
254
|
+
interactionState,
|
|
255
|
+
defaultInteractionState,
|
|
256
|
+
);
|
|
257
|
+
$: hasInteractionConfiguration =
|
|
258
|
+
annotations !== undefined ||
|
|
259
|
+
markers !== undefined ||
|
|
260
|
+
interactions !== undefined ||
|
|
261
|
+
interactionState !== undefined ||
|
|
262
|
+
defaultInteractionState !== undefined ||
|
|
263
|
+
onInteractionDiagnostic !== undefined ||
|
|
264
|
+
onInteractionEvent !== undefined ||
|
|
265
|
+
onInteractionStateChange !== undefined ||
|
|
266
|
+
marker !== undefined ||
|
|
267
|
+
tooltip !== undefined ||
|
|
268
|
+
popup !== undefined;
|
|
269
|
+
$: declarativeInteractionDiagnostics = [
|
|
270
|
+
...annotationResolution.diagnostics,
|
|
271
|
+
...interactionBindingResolution.diagnostics,
|
|
272
|
+
...interactionStateDiagnostics,
|
|
273
|
+
...(isImageMode && hasInteractionConfiguration
|
|
274
|
+
? [
|
|
275
|
+
createInteractionDiagnostic(
|
|
276
|
+
'UNSUPPORTED_MODE',
|
|
277
|
+
'Annotations, semantic interactions, and interaction state are unavailable in image mode.',
|
|
278
|
+
),
|
|
279
|
+
]
|
|
280
|
+
: []),
|
|
281
|
+
];
|
|
282
|
+
$: interactionDiagnostics = [
|
|
283
|
+
...declarativeInteractionDiagnostics,
|
|
284
|
+
...annotationRuntimeDiagnostics,
|
|
285
|
+
...semanticRuntimeDiagnostics,
|
|
286
|
+
...Object.values(bridgeInteractionDiagnostics),
|
|
287
|
+
];
|
|
288
|
+
$: hasInteractionErrors = interactionDiagnostics.some(({level}) => level === 'error');
|
|
289
|
+
$: effectiveInteractionCaptureState = hasInteractionErrors ? 'error' : interactionCaptureState;
|
|
290
|
+
$: captureState = combineCaptureStates(
|
|
291
|
+
combineCaptureStates(
|
|
292
|
+
combineCaptureStates(currentMapCaptureState, effectiveInteractionCaptureState),
|
|
293
|
+
runtimeResolutionState,
|
|
294
|
+
),
|
|
295
|
+
themeTransitionState,
|
|
296
|
+
);
|
|
297
|
+
$: if (mounted && hasInteractionErrors !== hadInteractionErrors) {
|
|
298
|
+
handleInteractionErrorState(hasInteractionErrors);
|
|
299
|
+
}
|
|
300
|
+
$: if (mounted) publishNewInteractionDiagnostics(interactionDiagnostics);
|
|
125
301
|
|
|
126
302
|
$: if (mounted) {
|
|
127
|
-
config;
|
|
128
303
|
imageUrl;
|
|
129
304
|
interactive;
|
|
130
|
-
manifestUrl;
|
|
131
|
-
map;
|
|
132
305
|
mapOptions;
|
|
133
|
-
mapStyle;
|
|
134
306
|
mode;
|
|
135
|
-
|
|
136
|
-
styleBaseUrl;
|
|
137
|
-
styleUrl;
|
|
138
|
-
themes;
|
|
307
|
+
source;
|
|
139
308
|
void refresh();
|
|
140
309
|
}
|
|
141
310
|
|
|
311
|
+
$: if (mounted) {
|
|
312
|
+
theme;
|
|
313
|
+
syncSystemColorSchemeSubscription();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
$: if (mounted) {
|
|
317
|
+
runtimeStyle;
|
|
318
|
+
void switchTheme();
|
|
319
|
+
}
|
|
320
|
+
|
|
142
321
|
$: if (mounted) {
|
|
143
322
|
center;
|
|
323
|
+
manifestMap;
|
|
144
324
|
zoom;
|
|
325
|
+
mapOptions?.bearing;
|
|
145
326
|
mapOptions?.center;
|
|
327
|
+
mapOptions?.pitch;
|
|
146
328
|
mapOptions?.zoom;
|
|
147
329
|
syncView();
|
|
148
330
|
}
|
|
149
331
|
|
|
150
332
|
$: if (mounted) {
|
|
333
|
+
annotations;
|
|
151
334
|
markers;
|
|
152
|
-
|
|
335
|
+
syncAnnotations();
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
$: if (mounted) {
|
|
339
|
+
interactions;
|
|
340
|
+
syncSemanticInteractions();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
$: if (mounted) {
|
|
344
|
+
defaultInteractionState;
|
|
345
|
+
interactionState;
|
|
346
|
+
interactionStateDiagnostics;
|
|
347
|
+
syncInteractionState();
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
$: if (mounted) {
|
|
351
|
+
marker;
|
|
352
|
+
popup;
|
|
353
|
+
tooltip;
|
|
354
|
+
syncCustomRenderers();
|
|
153
355
|
}
|
|
154
356
|
|
|
155
357
|
$: if (mounted && isImageMode) {
|
|
@@ -159,13 +361,19 @@
|
|
|
159
361
|
|
|
160
362
|
onMount(() => {
|
|
161
363
|
mounted = true;
|
|
364
|
+
syncSystemColorSchemeSubscription();
|
|
162
365
|
|
|
163
366
|
return () => {
|
|
164
367
|
mounted = false;
|
|
165
368
|
refreshRunId += 1;
|
|
166
369
|
manifestLoadId += 1;
|
|
370
|
+
unsubscribeSystemColorScheme?.();
|
|
167
371
|
imageResizeObserver?.disconnect();
|
|
168
|
-
|
|
372
|
+
try {
|
|
373
|
+
destroyMap();
|
|
374
|
+
} finally {
|
|
375
|
+
interactionCoordinator.dispose();
|
|
376
|
+
}
|
|
169
377
|
};
|
|
170
378
|
});
|
|
171
379
|
|
|
@@ -176,41 +384,41 @@
|
|
|
176
384
|
if (!mounted || runId !== refreshRunId) {
|
|
177
385
|
return;
|
|
178
386
|
}
|
|
179
|
-
|
|
180
387
|
updateImageResizeObserver();
|
|
181
|
-
recreateMap();
|
|
388
|
+
await recreateMap(runId);
|
|
182
389
|
}
|
|
183
390
|
|
|
184
391
|
async function refreshManifest() {
|
|
185
|
-
const shouldLoad =
|
|
186
|
-
|
|
187
|
-
imageMode: isImageMode,
|
|
188
|
-
imageUrl,
|
|
189
|
-
map,
|
|
190
|
-
style: mapStyle,
|
|
191
|
-
styleBaseUrl,
|
|
192
|
-
styleUrl,
|
|
193
|
-
});
|
|
392
|
+
const shouldLoad = shouldLoadManifest;
|
|
393
|
+
const requestKey = manifestRequestKey;
|
|
194
394
|
|
|
195
395
|
if (!shouldLoad) {
|
|
196
396
|
manifestLoadId += 1;
|
|
197
|
-
|
|
397
|
+
loadedManifestMap = null;
|
|
398
|
+
manifestResolutionKey = requestKey;
|
|
399
|
+
manifestResolutionState = 'not-needed';
|
|
198
400
|
return;
|
|
199
401
|
}
|
|
200
402
|
|
|
201
403
|
const loadId = ++manifestLoadId;
|
|
202
|
-
|
|
404
|
+
loadedManifestMap = null;
|
|
405
|
+
manifestResolutionKey = requestKey;
|
|
406
|
+
manifestResolutionState = 'loading';
|
|
203
407
|
|
|
204
408
|
try {
|
|
205
409
|
const manifest = await loadTileflowManifest(manifestUrl);
|
|
206
410
|
|
|
207
411
|
if (mounted && loadId === manifestLoadId) {
|
|
208
|
-
|
|
412
|
+
const resolvedMap =
|
|
413
|
+
manifest && mapName ? resolveTileflowManifestMap(manifest, mapName) : null;
|
|
414
|
+
loadedManifestMap = resolvedMap;
|
|
415
|
+
manifestResolutionState = resolvedMap ? 'ready' : 'error';
|
|
209
416
|
}
|
|
210
417
|
} catch (error) {
|
|
211
418
|
if (mounted && loadId === manifestLoadId) {
|
|
212
419
|
console.error('Failed to load Tileflow manifest', error);
|
|
213
|
-
|
|
420
|
+
loadedManifestMap = null;
|
|
421
|
+
manifestResolutionState = 'error';
|
|
214
422
|
}
|
|
215
423
|
}
|
|
216
424
|
}
|
|
@@ -237,46 +445,150 @@
|
|
|
237
445
|
}
|
|
238
446
|
|
|
239
447
|
function destroyMap() {
|
|
448
|
+
mapLoadId += 1;
|
|
240
449
|
readinessRunId += 1;
|
|
241
450
|
const lifecycleAttachment = mapLifecycleAttachment;
|
|
451
|
+
const fairUseNotice = mapFairUseNotice;
|
|
452
|
+
const annotationInteractionsForMap = annotationRuntime;
|
|
453
|
+
const semanticInteractionsForMap = semanticRuntime;
|
|
454
|
+
const detachAnnotationCoordinator = annotationRuntimeCoordinatorDetach;
|
|
455
|
+
const detachSemanticCoordinator = semanticRuntimeCoordinatorDetach;
|
|
456
|
+
const unsubscribeAnnotationDiagnostics = annotationRuntimeDiagnosticsUnsubscribe;
|
|
457
|
+
const unsubscribeAnnotationEvents = annotationRuntimeEventsUnsubscribe;
|
|
458
|
+
const unsubscribeAnnotationTargets = annotationRuntimeTargetsUnsubscribe;
|
|
459
|
+
const unsubscribeSemanticDiagnostics = semanticRuntimeDiagnosticsUnsubscribe;
|
|
460
|
+
const unsubscribeSemanticEvents = semanticRuntimeEventsUnsubscribe;
|
|
461
|
+
const unsubscribeSemanticTargets = semanticRuntimeTargetsUnsubscribe;
|
|
242
462
|
const currentMap = mapInstance;
|
|
463
|
+
const currentThemeController = themeController;
|
|
464
|
+
const worldRequestBridge = mapWorldRequestBridge;
|
|
465
|
+
interactionRuntimesDisposing = true;
|
|
466
|
+
mapFairUseNotice = null;
|
|
243
467
|
mapLifecycleAttachment = null;
|
|
244
468
|
mapInstance = null;
|
|
469
|
+
annotationRuntime = null;
|
|
470
|
+
annotationRuntimeCoordinatorDetach = null;
|
|
471
|
+
annotationRuntimeDiagnosticsUnsubscribe = null;
|
|
472
|
+
annotationRuntimeEventsUnsubscribe = null;
|
|
473
|
+
annotationRuntimeTargetsUnsubscribe = null;
|
|
474
|
+
semanticRuntime = null;
|
|
475
|
+
semanticRuntimeCoordinatorDetach = null;
|
|
476
|
+
semanticRuntimeDiagnosticsUnsubscribe = null;
|
|
477
|
+
semanticRuntimeEventsUnsubscribe = null;
|
|
478
|
+
semanticRuntimeTargetsUnsubscribe = null;
|
|
479
|
+
initializeSemanticRuntime = null;
|
|
480
|
+
annotationRenderTargets = [];
|
|
481
|
+
semanticRenderTargets = [];
|
|
482
|
+
interactionReadinessTargetKeys = [];
|
|
483
|
+
interactionReadinessRunId += 1;
|
|
484
|
+
interactionCaptureState = 'idle';
|
|
485
|
+
annotationRuntimeDiagnostics = [];
|
|
486
|
+
semanticRuntimeDiagnostics = [];
|
|
487
|
+
bridgeInteractionDiagnostics = {};
|
|
488
|
+
mapWorldRequestBridge = null;
|
|
489
|
+
themeController = null;
|
|
245
490
|
mapResizeObserver?.disconnect();
|
|
246
491
|
mapResizeObserver = null;
|
|
247
492
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
493
|
+
const errors: unknown[] = [];
|
|
494
|
+
const cleanupSteps = [
|
|
495
|
+
unsubscribeAnnotationTargets,
|
|
496
|
+
unsubscribeAnnotationDiagnostics,
|
|
497
|
+
unsubscribeAnnotationEvents,
|
|
498
|
+
unsubscribeSemanticTargets,
|
|
499
|
+
unsubscribeSemanticDiagnostics,
|
|
500
|
+
unsubscribeSemanticEvents,
|
|
501
|
+
detachAnnotationCoordinator,
|
|
502
|
+
detachSemanticCoordinator,
|
|
503
|
+
() => semanticInteractionsForMap?.dispose(),
|
|
504
|
+
() => annotationInteractionsForMap?.dispose(),
|
|
505
|
+
() => lifecycleAttachment?.dispose(),
|
|
506
|
+
() => currentThemeController?.dispose(),
|
|
507
|
+
() => worldRequestBridge?.dispose(),
|
|
508
|
+
() => fairUseNotice?.dispose(),
|
|
509
|
+
() => currentMap?.remove(),
|
|
510
|
+
];
|
|
511
|
+
|
|
512
|
+
for (const cleanup of cleanupSteps) {
|
|
513
|
+
if (!cleanup) continue;
|
|
251
514
|
try {
|
|
252
|
-
|
|
253
|
-
}
|
|
254
|
-
|
|
515
|
+
cleanup();
|
|
516
|
+
} catch (error) {
|
|
517
|
+
errors.push(error);
|
|
255
518
|
}
|
|
256
519
|
}
|
|
520
|
+
interactionRuntimesDisposing = false;
|
|
521
|
+
if (errors.length > 0) {
|
|
522
|
+
console.error(
|
|
523
|
+
'Failed to fully dispose the Tileflow map runtime',
|
|
524
|
+
errors.length === 1 ? errors[0] : new AggregateError(errors),
|
|
525
|
+
);
|
|
526
|
+
}
|
|
257
527
|
}
|
|
258
528
|
|
|
259
|
-
function recreateMap() {
|
|
529
|
+
async function recreateMap(runId: number) {
|
|
260
530
|
destroyMap();
|
|
261
|
-
|
|
531
|
+
mapCaptureState = 'loading';
|
|
532
|
+
themeTransitionState = 'idle';
|
|
262
533
|
|
|
263
|
-
if (
|
|
534
|
+
if (isImageMode) {
|
|
535
|
+
await markImageLoading();
|
|
264
536
|
return;
|
|
265
537
|
}
|
|
266
538
|
|
|
539
|
+
activeRuntimeResource = runtimeStyle;
|
|
540
|
+
if (!container || !runtimeStyle) {
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const loadId = mapLoadId;
|
|
545
|
+
const targetContainer = container;
|
|
267
546
|
const runtime = runtimeStyle;
|
|
547
|
+
let maplibregl: Awaited<ReturnType<typeof loadTileflowMapLibre>>;
|
|
548
|
+
|
|
549
|
+
try {
|
|
550
|
+
[maplibregl] = await Promise.all([
|
|
551
|
+
loadTileflowMapLibre(),
|
|
552
|
+
loadTileflowStyleFonts(runtime.style, {fontFaces: runtime.fontFaces}),
|
|
553
|
+
]);
|
|
554
|
+
} catch (error) {
|
|
555
|
+
if (mounted && runId === refreshRunId && loadId === mapLoadId) {
|
|
556
|
+
console.error('Failed to load the Tileflow map runtime', error);
|
|
557
|
+
mapCaptureState = 'error';
|
|
558
|
+
}
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
if (
|
|
563
|
+
!mounted ||
|
|
564
|
+
runId !== refreshRunId ||
|
|
565
|
+
loadId !== mapLoadId ||
|
|
566
|
+
container !== targetContainer ||
|
|
567
|
+
isImageMode
|
|
568
|
+
) {
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
|
|
268
572
|
const session = createTileflowSessionController({source: 'svelte'});
|
|
269
573
|
const sessionStarter = createTileflowSessionStarter({
|
|
270
574
|
getSessionId: () => session.sessionId,
|
|
271
575
|
sessionId: session.sessionId,
|
|
272
576
|
source: 'svelte',
|
|
273
577
|
});
|
|
578
|
+
mapFairUseNotice = attachTileflowFairUseNotice(targetContainer);
|
|
579
|
+
registerTileflowContourProtocol({addProtocol: maplibregl.addProtocol});
|
|
580
|
+
mapWorldRequestBridge = registerTileflowWorldRequestBridge({
|
|
581
|
+
addProtocol: maplibregl.addProtocol,
|
|
582
|
+
onNotice: mapFairUseNotice.update,
|
|
583
|
+
});
|
|
274
584
|
const maplibreMap = new maplibregl.Map({
|
|
275
585
|
...mapOptions,
|
|
276
586
|
attributionControl: mapOptions?.attributionControl ?? {compact: true},
|
|
587
|
+
bearing: resolvedBearing,
|
|
277
588
|
center: resolvedCenter as LngLatLike,
|
|
278
|
-
container,
|
|
589
|
+
container: targetContainer,
|
|
279
590
|
interactive: resolvedInteractive,
|
|
591
|
+
pitch: resolvedPitch,
|
|
280
592
|
style: runtime.style as StyleSpecification | string,
|
|
281
593
|
transformRequest: createTileflowTransformRequest({
|
|
282
594
|
always: true,
|
|
@@ -285,11 +597,156 @@
|
|
|
285
597
|
sessionController: session,
|
|
286
598
|
sessionId: session.sessionId,
|
|
287
599
|
transformRequest: mapOptions?.transformRequest ?? undefined,
|
|
600
|
+
worldRequestBridge: mapWorldRequestBridge,
|
|
288
601
|
}),
|
|
289
602
|
zoom: resolvedZoom,
|
|
290
603
|
});
|
|
291
604
|
|
|
292
605
|
mapInstance = maplibreMap;
|
|
606
|
+
themeController =
|
|
607
|
+
runtime.theme === undefined
|
|
608
|
+
? null
|
|
609
|
+
: createTileflowThemeController({
|
|
610
|
+
initial: runtime,
|
|
611
|
+
map: maplibreMap,
|
|
612
|
+
onTransition(transition) {
|
|
613
|
+
onThemeChange?.(transition);
|
|
614
|
+
if (transition.phase === 'preloading' || transition.phase === 'applying') {
|
|
615
|
+
themeTransitionState = 'loading';
|
|
616
|
+
} else if (transition.phase === 'error') {
|
|
617
|
+
themeTransitionState = 'error';
|
|
618
|
+
} else {
|
|
619
|
+
themeTransitionState = 'idle';
|
|
620
|
+
}
|
|
621
|
+
},
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
setBridgeInteractionDiagnostic('annotation-runtime', null);
|
|
625
|
+
try {
|
|
626
|
+
const annotationInteractionsForMap = createTileflowMapLibreDomRuntime<
|
|
627
|
+
MapLibreMap,
|
|
628
|
+
MapLibreMarker,
|
|
629
|
+
MapLibrePopup,
|
|
630
|
+
TileflowAnnotation
|
|
631
|
+
>({
|
|
632
|
+
createMarker({annotation, element}) {
|
|
633
|
+
element.title = legacyTitles.get(annotation.id) ?? annotation.ariaLabel;
|
|
634
|
+
return new maplibregl.Marker({element});
|
|
635
|
+
},
|
|
636
|
+
createOverlay({container: overlayContainer, kind}) {
|
|
637
|
+
return new maplibregl.Popup({
|
|
638
|
+
closeButton: false,
|
|
639
|
+
closeOnClick: false,
|
|
640
|
+
closeOnMove: false,
|
|
641
|
+
focusAfterOpen: false,
|
|
642
|
+
offset: kind === 'popup' ? 24 : 18,
|
|
643
|
+
}).setDOMContent(overlayContainer);
|
|
644
|
+
},
|
|
645
|
+
customMarker: marker !== undefined,
|
|
646
|
+
customPopup: popup !== undefined,
|
|
647
|
+
customTooltip: tooltip !== undefined,
|
|
648
|
+
document: targetContainer.ownerDocument,
|
|
649
|
+
interactionState: interactionCoordinator.getInteractionState(),
|
|
650
|
+
map: maplibreMap,
|
|
651
|
+
onInteractionStateChange: interactionCoordinator.requestInteractionState,
|
|
652
|
+
updateMarker(_markerInstance, {annotation, element}) {
|
|
653
|
+
element.title = legacyTitles.get(annotation.id) ?? annotation.ariaLabel;
|
|
654
|
+
},
|
|
655
|
+
});
|
|
656
|
+
annotationRuntime = annotationInteractionsForMap;
|
|
657
|
+
annotationRuntimeCoordinatorDetach = interactionCoordinator.attach(
|
|
658
|
+
'annotation',
|
|
659
|
+
annotationInteractionsForMap,
|
|
660
|
+
);
|
|
661
|
+
annotationRuntimeDiagnosticsUnsubscribe = annotationInteractionsForMap.subscribeDiagnostics(
|
|
662
|
+
(diagnostics) => {
|
|
663
|
+
annotationRuntimeDiagnostics = diagnostics;
|
|
664
|
+
},
|
|
665
|
+
);
|
|
666
|
+
annotationRuntimeEventsUnsubscribe = annotationInteractionsForMap.subscribeEvents((event) => {
|
|
667
|
+
if (!interactionRuntimesDisposing) onInteractionEvent?.(event);
|
|
668
|
+
});
|
|
669
|
+
annotationRuntimeTargetsUnsubscribe = annotationInteractionsForMap.subscribeRenderTargets(
|
|
670
|
+
(targets) => {
|
|
671
|
+
syncAnnotationRenderTargets(targets);
|
|
672
|
+
},
|
|
673
|
+
);
|
|
674
|
+
annotationRuntimeDiagnostics = annotationInteractionsForMap.getDiagnostics();
|
|
675
|
+
syncAnnotationRenderTargets(annotationInteractionsForMap.getRenderTargets());
|
|
676
|
+
syncAnnotations();
|
|
677
|
+
} catch (error) {
|
|
678
|
+
console.error('Failed to initialize Tileflow annotations', error);
|
|
679
|
+
setBridgeInteractionDiagnostic(
|
|
680
|
+
'annotation-runtime',
|
|
681
|
+
'Unable to initialize the Tileflow annotation runtime.',
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
initializeSemanticRuntime = () => {
|
|
686
|
+
if (semanticRuntime || interactionRuntimesDisposing || mapInstance !== maplibreMap) return;
|
|
687
|
+
const browserWindow = targetContainer.ownerDocument.defaultView;
|
|
688
|
+
if (!browserWindow) {
|
|
689
|
+
setBridgeInteractionDiagnostic(
|
|
690
|
+
'semantic-runtime',
|
|
691
|
+
'Semantic interactions require a browser document.',
|
|
692
|
+
);
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
setBridgeInteractionDiagnostic('semantic-runtime', null);
|
|
697
|
+
try {
|
|
698
|
+
const semanticInteractionsForMap = createTileflowMapLibreSemanticDomRuntime<
|
|
699
|
+
MapLibreMap,
|
|
700
|
+
MapLibrePopup
|
|
701
|
+
>({
|
|
702
|
+
cancelFrame: (frame) => browserWindow.cancelAnimationFrame(frame),
|
|
703
|
+
createOverlay({container: overlayContainer, kind}) {
|
|
704
|
+
return new maplibregl.Popup({
|
|
705
|
+
closeButton: false,
|
|
706
|
+
closeOnClick: false,
|
|
707
|
+
closeOnMove: false,
|
|
708
|
+
focusAfterOpen: false,
|
|
709
|
+
offset: kind === 'popup' ? 24 : 18,
|
|
710
|
+
}).setDOMContent(overlayContainer);
|
|
711
|
+
},
|
|
712
|
+
customPopup: popup !== undefined,
|
|
713
|
+
customTooltip: tooltip !== undefined,
|
|
714
|
+
document: targetContainer.ownerDocument,
|
|
715
|
+
interactionState: interactionCoordinator.getInteractionState(),
|
|
716
|
+
map: maplibreMap,
|
|
717
|
+
onInteractionStateChange: interactionCoordinator.requestInteractionState,
|
|
718
|
+
poiMap: createTileflowMapLibrePoiMap(maplibreMap),
|
|
719
|
+
requestFrame: (callback) => browserWindow.requestAnimationFrame(() => callback()),
|
|
720
|
+
});
|
|
721
|
+
semanticRuntime = semanticInteractionsForMap;
|
|
722
|
+
semanticRuntimeCoordinatorDetach = interactionCoordinator.attach(
|
|
723
|
+
'semantic',
|
|
724
|
+
semanticInteractionsForMap,
|
|
725
|
+
);
|
|
726
|
+
semanticRuntimeDiagnosticsUnsubscribe = semanticInteractionsForMap.subscribeDiagnostics(
|
|
727
|
+
(diagnostics) => {
|
|
728
|
+
semanticRuntimeDiagnostics = diagnostics;
|
|
729
|
+
},
|
|
730
|
+
);
|
|
731
|
+
semanticRuntimeEventsUnsubscribe = semanticInteractionsForMap.subscribeEvents((event) => {
|
|
732
|
+
if (!interactionRuntimesDisposing) onInteractionEvent?.(event);
|
|
733
|
+
});
|
|
734
|
+
semanticRuntimeTargetsUnsubscribe = semanticInteractionsForMap.subscribeRenderTargets(
|
|
735
|
+
(targets) => {
|
|
736
|
+
syncSemanticRenderTargets(targets);
|
|
737
|
+
},
|
|
738
|
+
);
|
|
739
|
+
semanticRuntimeDiagnostics = semanticInteractionsForMap.getDiagnostics();
|
|
740
|
+
syncSemanticRenderTargets(semanticInteractionsForMap.getRenderTargets());
|
|
741
|
+
} catch (error) {
|
|
742
|
+
console.error('Failed to initialize Tileflow semantic interactions', error);
|
|
743
|
+
setBridgeInteractionDiagnostic(
|
|
744
|
+
'semantic-runtime',
|
|
745
|
+
'Unable to initialize the Tileflow semantic interaction runtime.',
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
syncSemanticInteractions();
|
|
293
750
|
|
|
294
751
|
if (resolvedInteractive) {
|
|
295
752
|
maplibreMap.addControl(new maplibregl.NavigationControl(), 'top-right');
|
|
@@ -298,7 +755,7 @@
|
|
|
298
755
|
mapResizeObserver = new ResizeObserver(() => {
|
|
299
756
|
maplibreMap.resize();
|
|
300
757
|
});
|
|
301
|
-
mapResizeObserver.observe(
|
|
758
|
+
mapResizeObserver.observe(targetContainer);
|
|
302
759
|
|
|
303
760
|
mapLifecycleAttachment = attachTileflowMapLifecycle<MapLibreMap, number>({
|
|
304
761
|
getSession: () => {
|
|
@@ -306,7 +763,8 @@
|
|
|
306
763
|
return {
|
|
307
764
|
analytics: analyticsForLoad,
|
|
308
765
|
styleId:
|
|
309
|
-
analyticsForLoad?.styleId ??
|
|
766
|
+
analyticsForLoad?.styleId ??
|
|
767
|
+
(typeof runtime.style === 'string' ? runtime.style : mapName),
|
|
310
768
|
};
|
|
311
769
|
},
|
|
312
770
|
map: maplibreMap,
|
|
@@ -317,31 +775,129 @@
|
|
|
317
775
|
},
|
|
318
776
|
sessionStarter,
|
|
319
777
|
setState: (state) => {
|
|
320
|
-
|
|
778
|
+
mapCaptureState = state;
|
|
321
779
|
},
|
|
322
780
|
subscribe: (targetMap, event, listener) => {
|
|
323
781
|
const subscription = targetMap.on(event, listener);
|
|
324
782
|
return () => subscription.unsubscribe();
|
|
325
783
|
},
|
|
326
784
|
});
|
|
785
|
+
if (themeController && runtimeStyle && runtimeStyle !== runtime) {
|
|
786
|
+
activeRuntimeResource = runtimeStyle;
|
|
787
|
+
void themeController.setTheme(runtimeStyle);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
327
790
|
|
|
328
|
-
|
|
791
|
+
async function switchTheme() {
|
|
792
|
+
if (!themeController) return;
|
|
793
|
+
if (themeResolution.error) {
|
|
794
|
+
await themeController.setTheme(themeController.getCurrent());
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
const next = runtimeStyle;
|
|
798
|
+
if (!next) return;
|
|
799
|
+
activeRuntimeResource = next;
|
|
800
|
+
const result = await themeController.setTheme(next);
|
|
801
|
+
if (result.status === 'failed') {
|
|
802
|
+
console.error('Failed to change the Tileflow map theme', result.error);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
function syncSystemColorSchemeSubscription() {
|
|
807
|
+
unsubscribeSystemColorScheme?.();
|
|
808
|
+
unsubscribeSystemColorScheme = null;
|
|
809
|
+
if (theme !== 'system') return;
|
|
810
|
+
systemColorScheme = getTileflowSystemColorScheme();
|
|
811
|
+
unsubscribeSystemColorScheme = subscribeTileflowSystemColorScheme((scheme) => {
|
|
812
|
+
systemColorScheme = scheme;
|
|
813
|
+
});
|
|
329
814
|
}
|
|
330
815
|
|
|
331
816
|
function syncView() {
|
|
332
817
|
mapInstance?.jumpTo({
|
|
818
|
+
bearing: resolvedBearing,
|
|
333
819
|
center: resolvedCenter as LngLatLike,
|
|
820
|
+
pitch: resolvedPitch,
|
|
334
821
|
zoom: resolvedZoom,
|
|
335
822
|
});
|
|
336
823
|
}
|
|
337
824
|
|
|
338
|
-
function
|
|
339
|
-
|
|
340
|
-
|
|
825
|
+
function syncAnnotations() {
|
|
826
|
+
legacyTitles = annotationResolution.titles;
|
|
827
|
+
if (!annotationRuntime) return;
|
|
828
|
+
|
|
829
|
+
try {
|
|
830
|
+
annotationRuntime.reconcile(
|
|
831
|
+
annotationResolution.ok && !isImageMode ? annotationResolution.annotations : [],
|
|
832
|
+
);
|
|
833
|
+
void settleInteractionReadiness();
|
|
834
|
+
setBridgeInteractionDiagnostic('annotation-runtime', null);
|
|
835
|
+
} catch (error) {
|
|
836
|
+
console.error('Failed to reconcile Tileflow annotations', error);
|
|
837
|
+
setBridgeInteractionDiagnostic(
|
|
838
|
+
'annotation-runtime',
|
|
839
|
+
'Unable to reconcile the Tileflow annotation runtime.',
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
function syncSemanticInteractions() {
|
|
845
|
+
const bindings =
|
|
846
|
+
interactionBindingResolution.ok && !isImageMode ? interactionBindingResolution.bindings : [];
|
|
847
|
+
if (bindings.length > 0 && !semanticRuntime) initializeSemanticRuntime?.();
|
|
848
|
+
if (!semanticRuntime) return;
|
|
849
|
+
|
|
850
|
+
try {
|
|
851
|
+
semanticRuntime.reconcile(bindings);
|
|
852
|
+
void settleInteractionReadiness();
|
|
853
|
+
setBridgeInteractionDiagnostic('semantic-runtime', null);
|
|
854
|
+
} catch (error) {
|
|
855
|
+
console.error('Failed to reconcile Tileflow semantic interactions', error);
|
|
856
|
+
setBridgeInteractionDiagnostic(
|
|
857
|
+
'semantic-runtime',
|
|
858
|
+
'Unable to reconcile the Tileflow semantic interaction runtime.',
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function syncInteractionState() {
|
|
864
|
+
if (
|
|
865
|
+
!controlledInteractionOwnership ||
|
|
866
|
+
interactionState === undefined ||
|
|
867
|
+
interactionStateDiagnostics.length > 0
|
|
868
|
+
) {
|
|
341
869
|
return;
|
|
342
870
|
}
|
|
871
|
+
try {
|
|
872
|
+
interactionCoordinator.setInteractionState(interactionState);
|
|
873
|
+
setBridgeInteractionDiagnostic('state', null);
|
|
874
|
+
} catch (error) {
|
|
875
|
+
console.error('Failed to synchronize Tileflow interaction state', error);
|
|
876
|
+
setBridgeInteractionDiagnostic('state', 'Unable to synchronize Tileflow interaction state.');
|
|
877
|
+
}
|
|
878
|
+
}
|
|
343
879
|
|
|
344
|
-
|
|
880
|
+
function syncCustomRenderers() {
|
|
881
|
+
if (!annotationRuntime && !semanticRuntime) return;
|
|
882
|
+
try {
|
|
883
|
+
annotationRuntime?.setCustomRenderers({
|
|
884
|
+
marker: marker !== undefined,
|
|
885
|
+
popup: popup !== undefined,
|
|
886
|
+
tooltip: tooltip !== undefined,
|
|
887
|
+
});
|
|
888
|
+
semanticRuntime?.setCustomRenderers({
|
|
889
|
+
popup: popup !== undefined,
|
|
890
|
+
tooltip: tooltip !== undefined,
|
|
891
|
+
});
|
|
892
|
+
void settleInteractionReadiness();
|
|
893
|
+
setBridgeInteractionDiagnostic('renderers', null);
|
|
894
|
+
} catch (error) {
|
|
895
|
+
console.error('Failed to synchronize Tileflow interaction snippets', error);
|
|
896
|
+
setBridgeInteractionDiagnostic(
|
|
897
|
+
'renderers',
|
|
898
|
+
'Unable to synchronize Tileflow interaction snippets.',
|
|
899
|
+
);
|
|
900
|
+
}
|
|
345
901
|
}
|
|
346
902
|
|
|
347
903
|
async function handleImageLoad(event: Event) {
|
|
@@ -354,22 +910,24 @@
|
|
|
354
910
|
if (typeof image.decode === 'function') await image.decode();
|
|
355
911
|
} catch {
|
|
356
912
|
if (!image.complete || image.naturalWidth === 0) {
|
|
357
|
-
if (runId === readinessRunId)
|
|
913
|
+
if (runId === readinessRunId) mapCaptureState = 'error';
|
|
358
914
|
return;
|
|
359
915
|
}
|
|
360
916
|
}
|
|
361
|
-
if (runId === readinessRunId)
|
|
917
|
+
if (runId === readinessRunId) mapCaptureState = 'idle';
|
|
362
918
|
}
|
|
363
919
|
|
|
364
920
|
async function markImageLoading() {
|
|
365
921
|
const runId = ++readinessRunId;
|
|
366
|
-
|
|
922
|
+
activeRuntimeResource = runtimeImageUrl;
|
|
923
|
+
mapCaptureState = hasInteractionConfiguration ? 'error' : 'loading';
|
|
924
|
+
if (hasInteractionConfiguration) return;
|
|
367
925
|
await tick();
|
|
368
926
|
if (!mounted || runId !== readinessRunId) return;
|
|
369
927
|
const image = container?.querySelector('img');
|
|
370
928
|
if (!image?.complete) return;
|
|
371
929
|
if (image.naturalWidth === 0) {
|
|
372
|
-
|
|
930
|
+
mapCaptureState = 'error';
|
|
373
931
|
return;
|
|
374
932
|
}
|
|
375
933
|
await markImageReady(image);
|
|
@@ -377,19 +935,375 @@
|
|
|
377
935
|
|
|
378
936
|
function handleImageError() {
|
|
379
937
|
readinessRunId += 1;
|
|
380
|
-
|
|
938
|
+
mapCaptureState = 'error';
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
type AnnotationResolution = Readonly<{
|
|
942
|
+
annotations: readonly TileflowAnnotation[];
|
|
943
|
+
diagnostics: readonly TileflowInteractionDiagnostic[];
|
|
944
|
+
ok: boolean;
|
|
945
|
+
titles: ReadonlyMap<string, string>;
|
|
946
|
+
}>;
|
|
947
|
+
|
|
948
|
+
type InteractionBindingResolution = Readonly<{
|
|
949
|
+
bindings: readonly TileflowInteractionBinding[];
|
|
950
|
+
diagnostics: readonly TileflowInteractionDiagnostic[];
|
|
951
|
+
ok: boolean;
|
|
952
|
+
}>;
|
|
953
|
+
|
|
954
|
+
function resolveAnnotationInput(
|
|
955
|
+
nextAnnotations: readonly TileflowAnnotation[] | undefined,
|
|
956
|
+
nextMarkers: readonly TileflowMapMarker[] | undefined,
|
|
957
|
+
): AnnotationResolution {
|
|
958
|
+
if (nextAnnotations !== undefined && nextMarkers !== undefined) {
|
|
959
|
+
return {
|
|
960
|
+
annotations: [],
|
|
961
|
+
diagnostics: [
|
|
962
|
+
createInteractionDiagnostic(
|
|
963
|
+
'INVALID_DOCUMENT',
|
|
964
|
+
'The annotations and legacy markers props are mutually exclusive.',
|
|
965
|
+
),
|
|
966
|
+
],
|
|
967
|
+
ok: false,
|
|
968
|
+
titles: new Map(),
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
const normalized = normalizeTileflowLegacyMarkers(nextMarkers ?? []);
|
|
973
|
+
const candidates = nextAnnotations ?? normalized.annotations;
|
|
974
|
+
const titles =
|
|
975
|
+
nextAnnotations === undefined
|
|
976
|
+
? normalized.titles
|
|
977
|
+
: new Map(nextAnnotations.map((annotation) => [annotation.id, annotation.ariaLabel]));
|
|
978
|
+
const validation = validateTileflowAnnotations(candidates);
|
|
979
|
+
if (!validation.ok) {
|
|
980
|
+
return {
|
|
981
|
+
annotations: [],
|
|
982
|
+
diagnostics: validation.diagnostics,
|
|
983
|
+
ok: false,
|
|
984
|
+
titles,
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
return {annotations: candidates, diagnostics: [], ok: true, titles};
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
function resolveInteractionBindingInput(
|
|
992
|
+
nextInteractions: readonly TileflowInteractionBinding[] | undefined,
|
|
993
|
+
): InteractionBindingResolution {
|
|
994
|
+
const candidates = nextInteractions ?? [];
|
|
995
|
+
const validation = validateTileflowInteractionBindings(candidates);
|
|
996
|
+
if (!validation.ok) {
|
|
997
|
+
return {bindings: [], diagnostics: validation.diagnostics, ok: false};
|
|
998
|
+
}
|
|
999
|
+
return {bindings: candidates, diagnostics: [], ok: true};
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
function validateInteractionStateInput(
|
|
1003
|
+
controlledState: TileflowInteractionState | undefined,
|
|
1004
|
+
defaultState: TileflowInteractionState | undefined,
|
|
1005
|
+
): readonly TileflowInteractionDiagnostic[] {
|
|
1006
|
+
if (controlledState !== undefined && defaultState !== undefined) {
|
|
1007
|
+
return [
|
|
1008
|
+
createInteractionDiagnostic(
|
|
1009
|
+
'INVALID_DOCUMENT',
|
|
1010
|
+
'interactionState and defaultInteractionState are mutually exclusive.',
|
|
1011
|
+
),
|
|
1012
|
+
];
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
if ((controlledState !== undefined) !== controlledInteractionOwnership) {
|
|
1016
|
+
return [
|
|
1017
|
+
createInteractionDiagnostic(
|
|
1018
|
+
'INVALID_DOCUMENT',
|
|
1019
|
+
'Tileflow interaction state ownership cannot switch between controlled and uncontrolled.',
|
|
1020
|
+
),
|
|
1021
|
+
];
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
const candidate = controlledState ?? defaultState;
|
|
1025
|
+
if (candidate === undefined) return [];
|
|
1026
|
+
const parsed = tileflowInteractionStateSchema.safeParse(candidate);
|
|
1027
|
+
if (parsed.success) return [];
|
|
1028
|
+
const root = controlledState === undefined ? '/defaultInteractionState' : '/interactionState';
|
|
1029
|
+
return parsed.error.issues.map((issue) =>
|
|
1030
|
+
createInteractionDiagnostic(
|
|
1031
|
+
'INVALID_DOCUMENT',
|
|
1032
|
+
issue.message,
|
|
1033
|
+
`${root}${jsonPointer(issue.path)}`,
|
|
1034
|
+
),
|
|
1035
|
+
);
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
function createInteractionDiagnostic(
|
|
1039
|
+
code: TileflowInteractionDiagnosticCode,
|
|
1040
|
+
message: string,
|
|
1041
|
+
path?: string,
|
|
1042
|
+
): TileflowInteractionDiagnostic {
|
|
1043
|
+
return {
|
|
1044
|
+
code,
|
|
1045
|
+
level: 'error',
|
|
1046
|
+
message,
|
|
1047
|
+
...(path === undefined ? {} : {path}),
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
type BridgeInteractionDiagnosticSource =
|
|
1052
|
+
| 'annotation-runtime'
|
|
1053
|
+
| 'portal'
|
|
1054
|
+
| 'renderers'
|
|
1055
|
+
| 'semantic-runtime'
|
|
1056
|
+
| 'state';
|
|
1057
|
+
|
|
1058
|
+
function setBridgeInteractionDiagnostic(
|
|
1059
|
+
source: BridgeInteractionDiagnosticSource,
|
|
1060
|
+
message: string | null,
|
|
1061
|
+
) {
|
|
1062
|
+
const current = bridgeInteractionDiagnostics[source];
|
|
1063
|
+
if (message === null ? current === undefined : current?.message === message) return;
|
|
1064
|
+
const next = {...bridgeInteractionDiagnostics};
|
|
1065
|
+
if (message === null) delete next[source];
|
|
1066
|
+
else next[source] = createInteractionDiagnostic('OVERLAY_FAILURE', message);
|
|
1067
|
+
bridgeInteractionDiagnostics = next;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
function publishNewInteractionDiagnostics(diagnostics: readonly TileflowInteractionDiagnostic[]) {
|
|
1071
|
+
const nextKeys = new Set(diagnostics.map(interactionDiagnosticKey));
|
|
1072
|
+
for (const diagnostic of diagnostics) {
|
|
1073
|
+
if (!activeInteractionDiagnosticKeys.has(interactionDiagnosticKey(diagnostic))) {
|
|
1074
|
+
onInteractionDiagnostic?.(diagnostic);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
activeInteractionDiagnosticKeys = nextKeys;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
function syncAnnotationRenderTargets(
|
|
1081
|
+
targets: readonly TileflowMapLibreDomRenderTarget<TileflowAnnotation>[],
|
|
1082
|
+
) {
|
|
1083
|
+
annotationRenderTargets = targets;
|
|
1084
|
+
syncInteractionRenderTargetReadiness();
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
function syncSemanticRenderTargets(targets: readonly TileflowMapLibreSemanticDomRenderTarget[]) {
|
|
1088
|
+
semanticRenderTargets = targets;
|
|
1089
|
+
syncInteractionRenderTargetReadiness();
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
function syncInteractionRenderTargetReadiness() {
|
|
1093
|
+
const nextKeys = [
|
|
1094
|
+
...annotationRenderTargets.map(({key}) => `annotation:${key}`),
|
|
1095
|
+
...semanticRenderTargets.map(({key}) => `semantic:${key}`),
|
|
1096
|
+
];
|
|
1097
|
+
if (
|
|
1098
|
+
nextKeys.length === interactionReadinessTargetKeys.length &&
|
|
1099
|
+
nextKeys.every((key, index) => key === interactionReadinessTargetKeys[index])
|
|
1100
|
+
) {
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
interactionReadinessTargetKeys = nextKeys;
|
|
1104
|
+
void settleInteractionReadiness();
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
async function settleInteractionReadiness() {
|
|
1108
|
+
const runId = ++interactionReadinessRunId;
|
|
1109
|
+
if (!mounted || hasInteractionErrors) return;
|
|
1110
|
+
interactionCaptureState = 'loading';
|
|
1111
|
+
await tick();
|
|
1112
|
+
if (!mounted || runId !== interactionReadinessRunId || hasInteractionErrors) return;
|
|
1113
|
+
await nextAnimationFrame();
|
|
1114
|
+
if (!mounted || runId !== interactionReadinessRunId || hasInteractionErrors) return;
|
|
1115
|
+
await nextAnimationFrame();
|
|
1116
|
+
if (!mounted || runId !== interactionReadinessRunId || hasInteractionErrors) return;
|
|
1117
|
+
interactionCaptureState = 'idle';
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
function handleInteractionErrorState(hasErrors: boolean) {
|
|
1121
|
+
hadInteractionErrors = hasErrors;
|
|
1122
|
+
interactionReadinessRunId += 1;
|
|
1123
|
+
if (!hasErrors) void settleInteractionReadiness();
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
function nextAnimationFrame(): Promise<void> {
|
|
1127
|
+
const view = container?.ownerDocument.defaultView;
|
|
1128
|
+
if (!view) return Promise.resolve();
|
|
1129
|
+
return new Promise((resolve) => view.requestAnimationFrame(() => resolve()));
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
function combineCaptureStates(
|
|
1133
|
+
mapState: 'error' | 'idle' | 'loading',
|
|
1134
|
+
interactionStateForCapture: 'error' | 'idle' | 'loading',
|
|
1135
|
+
): 'error' | 'idle' | 'loading' {
|
|
1136
|
+
if (mapState === 'error' || interactionStateForCapture === 'error') return 'error';
|
|
1137
|
+
return mapState === 'idle' && interactionStateForCapture === 'idle' ? 'idle' : 'loading';
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
function interactionDiagnosticKey(diagnostic: TileflowInteractionDiagnostic): string {
|
|
1141
|
+
return JSON.stringify([
|
|
1142
|
+
diagnostic.code,
|
|
1143
|
+
diagnostic.message,
|
|
1144
|
+
diagnostic.path,
|
|
1145
|
+
diagnostic.target,
|
|
1146
|
+
]);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
function jsonPointer(path: readonly PropertyKey[]): string {
|
|
1150
|
+
return path
|
|
1151
|
+
.map((segment) => String(segment).replaceAll('~', '~0').replaceAll('/', '~1'))
|
|
1152
|
+
.map((segment) => `/${segment}`)
|
|
1153
|
+
.join('');
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
function portal(node: HTMLElement, target: HTMLElement) {
|
|
1157
|
+
let currentTarget = target;
|
|
1158
|
+
const move = (nextTarget: HTMLElement) => {
|
|
1159
|
+
currentTarget = nextTarget;
|
|
1160
|
+
currentTarget.append(node);
|
|
1161
|
+
setBridgeInteractionDiagnostic('portal', null);
|
|
1162
|
+
};
|
|
1163
|
+
|
|
1164
|
+
try {
|
|
1165
|
+
move(target);
|
|
1166
|
+
} catch (error) {
|
|
1167
|
+
console.error('Failed to mount a Tileflow interaction snippet', error);
|
|
1168
|
+
setBridgeInteractionDiagnostic('portal', 'Unable to mount a Tileflow interaction snippet.');
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
return {
|
|
1172
|
+
destroy() {
|
|
1173
|
+
node.remove();
|
|
1174
|
+
},
|
|
1175
|
+
update(nextTarget: HTMLElement) {
|
|
1176
|
+
if (nextTarget === currentTarget) return;
|
|
1177
|
+
try {
|
|
1178
|
+
move(nextTarget);
|
|
1179
|
+
} catch (error) {
|
|
1180
|
+
console.error('Failed to move a Tileflow interaction snippet', error);
|
|
1181
|
+
setBridgeInteractionDiagnostic(
|
|
1182
|
+
'portal',
|
|
1183
|
+
'Unable to mount a Tileflow interaction snippet.',
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
1186
|
+
},
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
function createAnnotationViewContext(
|
|
1191
|
+
renderTarget: TileflowMapLibreDomRenderTarget<TileflowAnnotation>,
|
|
1192
|
+
): TileflowAnnotationViewContext {
|
|
1193
|
+
const content = getRenderTargetContent(renderTarget);
|
|
1194
|
+
return {
|
|
1195
|
+
annotation: renderTarget.annotation,
|
|
1196
|
+
close: renderTarget.close,
|
|
1197
|
+
content,
|
|
1198
|
+
target: {
|
|
1199
|
+
annotation: renderTarget.annotation,
|
|
1200
|
+
coordinate: renderTarget.annotation.coordinate,
|
|
1201
|
+
kind: 'annotation',
|
|
1202
|
+
},
|
|
1203
|
+
viewName: content?.kind === 'view' ? content.name : undefined,
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
function getRenderTargetContent(
|
|
1208
|
+
renderTarget: TileflowMapLibreDomRenderTarget<TileflowAnnotation>,
|
|
1209
|
+
): TileflowInteractionContent | undefined {
|
|
1210
|
+
switch (renderTarget.kind) {
|
|
1211
|
+
case 'marker':
|
|
1212
|
+
return renderTarget.annotation.marker?.content;
|
|
1213
|
+
case 'popup':
|
|
1214
|
+
return renderTarget.annotation.popup?.content;
|
|
1215
|
+
case 'tooltip':
|
|
1216
|
+
return renderTarget.annotation.tooltip?.content;
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
function createSemanticViewContext(
|
|
1221
|
+
renderTarget: TileflowMapLibreSemanticDomRenderTarget,
|
|
1222
|
+
): TileflowInteractionViewContext {
|
|
1223
|
+
return {
|
|
1224
|
+
close: renderTarget.close,
|
|
1225
|
+
content: renderTarget.content,
|
|
1226
|
+
target: renderTarget.target,
|
|
1227
|
+
viewName: renderTarget.viewName,
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
function createTileflowMapLibrePoiMap(map: MapLibreMap): TileflowMapLibrePoiMap {
|
|
1232
|
+
return {
|
|
1233
|
+
getStyle: () => map.getStyle(),
|
|
1234
|
+
on: (event, listener) => map.on(event, listener),
|
|
1235
|
+
queryRenderedFeatures(point, options) {
|
|
1236
|
+
return map
|
|
1237
|
+
.queryRenderedFeatures(point as PointLike, {layers: [...options.layers]})
|
|
1238
|
+
.map((feature) => ({
|
|
1239
|
+
...(feature.id === undefined ? {} : {id: feature.id}),
|
|
1240
|
+
layer: {id: feature.layer.id},
|
|
1241
|
+
...(feature.properties === null ? {} : {properties: feature.properties}),
|
|
1242
|
+
...(feature.source === undefined ? {} : {source: feature.source}),
|
|
1243
|
+
...(feature.sourceLayer === undefined ? {} : {sourceLayer: feature.sourceLayer}),
|
|
1244
|
+
}));
|
|
1245
|
+
},
|
|
1246
|
+
};
|
|
381
1247
|
}
|
|
382
1248
|
|
|
383
1249
|
function formatHeight(value: number | string): string {
|
|
384
1250
|
return typeof value === 'number' ? `${value}px` : value;
|
|
385
1251
|
}
|
|
1252
|
+
|
|
1253
|
+
function resolveThemeSelection(input: {
|
|
1254
|
+
colorScheme: 'dark' | 'light';
|
|
1255
|
+
manifestMap: TileflowRuntimeManifestMap | null;
|
|
1256
|
+
source: TileflowMapProps['source'];
|
|
1257
|
+
theme: TileflowMapProps['theme'];
|
|
1258
|
+
}): Readonly<{error: boolean; name: string | undefined}> {
|
|
1259
|
+
if (!input.manifestMap || input.source.kind !== 'tileflow') {
|
|
1260
|
+
return {
|
|
1261
|
+
error: false,
|
|
1262
|
+
name:
|
|
1263
|
+
input.source.kind === 'tileflow' && input.theme && input.theme !== 'system'
|
|
1264
|
+
? input.theme
|
|
1265
|
+
: undefined,
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
try {
|
|
1270
|
+
return {
|
|
1271
|
+
error: false,
|
|
1272
|
+
name: resolveTileflowRuntimeTheme(input.manifestMap, input.theme, input.colorScheme).name,
|
|
1273
|
+
};
|
|
1274
|
+
} catch {
|
|
1275
|
+
return {error: true, name: undefined};
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
function resolveRuntimeResolutionState(input: {
|
|
1280
|
+
currentManifestResolutionState: 'error' | 'loading' | 'not-needed' | 'ready';
|
|
1281
|
+
imageSize: {height: number; width: number} | null;
|
|
1282
|
+
imageUrl: string | undefined;
|
|
1283
|
+
isImageMode: boolean;
|
|
1284
|
+
runtimeImageUrl: string | undefined;
|
|
1285
|
+
runtimeStyle: ReturnType<typeof resolveTileflowRuntimeStyle>;
|
|
1286
|
+
shouldLoadManifest: boolean;
|
|
1287
|
+
themeResolutionError: boolean;
|
|
1288
|
+
}): 'error' | 'idle' | 'loading' {
|
|
1289
|
+
if (input.shouldLoadManifest && input.currentManifestResolutionState !== 'ready') {
|
|
1290
|
+
return input.currentManifestResolutionState === 'error' ? 'error' : 'loading';
|
|
1291
|
+
}
|
|
1292
|
+
if (input.themeResolutionError) return 'error';
|
|
1293
|
+
if (input.isImageMode) {
|
|
1294
|
+
if (input.runtimeImageUrl) return 'idle';
|
|
1295
|
+
return input.imageUrl === undefined && input.imageSize === null ? 'loading' : 'error';
|
|
1296
|
+
}
|
|
1297
|
+
return input.runtimeStyle ? 'idle' : 'error';
|
|
1298
|
+
}
|
|
386
1299
|
</script>
|
|
387
1300
|
|
|
388
1301
|
<div
|
|
389
1302
|
bind:this={container}
|
|
390
1303
|
class={className}
|
|
391
1304
|
data-tileflow-capture-id={resolvedCaptureId}
|
|
392
|
-
data-tileflow-map={
|
|
1305
|
+
data-tileflow-map={mapName}
|
|
1306
|
+
data-tileflow-theme={resolvedThemeName}
|
|
393
1307
|
data-tileflow-state={captureState}
|
|
394
1308
|
style={frameStyle}
|
|
395
1309
|
>
|
|
@@ -404,4 +1318,30 @@
|
|
|
404
1318
|
style="display: block; height: 100%; object-fit: cover; width: 100%;"
|
|
405
1319
|
/>
|
|
406
1320
|
{/if}
|
|
1321
|
+
{#each annotationRenderTargets as renderTarget (renderTarget.key)}
|
|
1322
|
+
{#if renderTarget.kind === 'marker' && marker}
|
|
1323
|
+
<div style="display: contents;" use:portal={renderTarget.container}>
|
|
1324
|
+
{@render marker(createAnnotationViewContext(renderTarget))}
|
|
1325
|
+
</div>
|
|
1326
|
+
{:else if renderTarget.kind === 'tooltip' && tooltip}
|
|
1327
|
+
<div style="display: contents;" use:portal={renderTarget.container}>
|
|
1328
|
+
{@render tooltip(createAnnotationViewContext(renderTarget))}
|
|
1329
|
+
</div>
|
|
1330
|
+
{:else if renderTarget.kind === 'popup' && popup}
|
|
1331
|
+
<div style="display: contents;" use:portal={renderTarget.container}>
|
|
1332
|
+
{@render popup(createAnnotationViewContext(renderTarget))}
|
|
1333
|
+
</div>
|
|
1334
|
+
{/if}
|
|
1335
|
+
{/each}
|
|
1336
|
+
{#each semanticRenderTargets as renderTarget (renderTarget.key)}
|
|
1337
|
+
{#if renderTarget.kind === 'tooltip' && tooltip}
|
|
1338
|
+
<div style="display: contents;" use:portal={renderTarget.container}>
|
|
1339
|
+
{@render tooltip(createSemanticViewContext(renderTarget))}
|
|
1340
|
+
</div>
|
|
1341
|
+
{:else if renderTarget.kind === 'popup' && popup}
|
|
1342
|
+
<div style="display: contents;" use:portal={renderTarget.container}>
|
|
1343
|
+
{@render popup(createSemanticViewContext(renderTarget))}
|
|
1344
|
+
</div>
|
|
1345
|
+
{/if}
|
|
1346
|
+
{/each}
|
|
407
1347
|
</div>
|