@tileflow/svelte 0.1.0-alpha.18 → 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 -125
- package/src/index.d.ts +91 -21
- 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,158 +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 tileBaseUrl: string | undefined = undefined;
|
|
62
|
-
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;
|
|
63
103
|
|
|
64
104
|
let container: HTMLDivElement;
|
|
65
|
-
let
|
|
105
|
+
let mapCaptureState: 'error' | 'idle' | 'loading' = 'loading';
|
|
66
106
|
let imageSize: {height: number; width: number} | null = null;
|
|
67
107
|
let imageResizeObserver: ResizeObserver | null = null;
|
|
68
108
|
let mapInstance: MapLibreMap | null = null;
|
|
109
|
+
let activeRuntimeResource: unknown = undefined;
|
|
110
|
+
let mapFairUseNotice: TileflowFairUseNoticeController | null = null;
|
|
69
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();
|
|
70
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;
|
|
71
147
|
let manifestMap: TileflowRuntimeManifestMap | null = null;
|
|
72
148
|
let manifestLoadId = 0;
|
|
149
|
+
let manifestResolutionKey = '';
|
|
150
|
+
let manifestResolutionState: 'error' | 'loading' | 'not-needed' | 'ready' = 'loading';
|
|
151
|
+
let mapLoadId = 0;
|
|
73
152
|
let mounted = false;
|
|
74
153
|
let refreshRunId = 0;
|
|
75
154
|
let readinessRunId = 0;
|
|
76
155
|
|
|
77
156
|
const dispatch = createEventDispatcher<{load: MapLibreMap}>();
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
$:
|
|
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});
|
|
95
174
|
$: resolvedCaptureId = normalizeTileflowCaptureId(captureId);
|
|
96
175
|
$: isImageMode = resolvedMode === 'image';
|
|
97
|
-
$:
|
|
98
|
-
$:
|
|
99
|
-
|
|
100
|
-
|
|
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;
|
|
101
213
|
$: resolvedInteractive = interactive ?? mapOptions?.interactive ?? true;
|
|
102
|
-
$: runtimeStyle =
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
styleUrl,
|
|
112
|
-
themes,
|
|
113
|
-
tileBaseUrl,
|
|
114
|
-
});
|
|
214
|
+
$: runtimeStyle =
|
|
215
|
+
isImageMode || themeResolution.error
|
|
216
|
+
? null
|
|
217
|
+
: resolveTileflowRuntimeStyle({
|
|
218
|
+
colorScheme: systemColorScheme,
|
|
219
|
+
manifestMap,
|
|
220
|
+
source,
|
|
221
|
+
theme: themeResolution.name,
|
|
222
|
+
});
|
|
115
223
|
$: resolvedAnalytics = mergeTileflowAnalytics(analytics, runtimeStyle?.analytics);
|
|
116
224
|
$: runtimeImageUrl =
|
|
117
225
|
imageUrl ??
|
|
118
|
-
(isImageMode
|
|
226
|
+
(isImageMode && !themeResolution.error
|
|
119
227
|
? resolveTileflowStaticImageUrl({
|
|
120
228
|
center: imageCenter,
|
|
229
|
+
colorScheme: systemColorScheme,
|
|
121
230
|
imageSize,
|
|
122
231
|
manifestMap,
|
|
232
|
+
theme: themeResolution.name,
|
|
123
233
|
zoom: imageZoom,
|
|
124
234
|
})
|
|
125
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';
|
|
126
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);
|
|
127
301
|
|
|
128
302
|
$: if (mounted) {
|
|
129
|
-
config;
|
|
130
303
|
imageUrl;
|
|
131
304
|
interactive;
|
|
132
|
-
manifestUrl;
|
|
133
|
-
map;
|
|
134
305
|
mapOptions;
|
|
135
|
-
mapStyle;
|
|
136
306
|
mode;
|
|
137
|
-
|
|
138
|
-
styleBaseUrl;
|
|
139
|
-
styleUrl;
|
|
140
|
-
themes;
|
|
141
|
-
tileBaseUrl;
|
|
307
|
+
source;
|
|
142
308
|
void refresh();
|
|
143
309
|
}
|
|
144
310
|
|
|
311
|
+
$: if (mounted) {
|
|
312
|
+
theme;
|
|
313
|
+
syncSystemColorSchemeSubscription();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
$: if (mounted) {
|
|
317
|
+
runtimeStyle;
|
|
318
|
+
void switchTheme();
|
|
319
|
+
}
|
|
320
|
+
|
|
145
321
|
$: if (mounted) {
|
|
146
322
|
center;
|
|
323
|
+
manifestMap;
|
|
147
324
|
zoom;
|
|
325
|
+
mapOptions?.bearing;
|
|
148
326
|
mapOptions?.center;
|
|
327
|
+
mapOptions?.pitch;
|
|
149
328
|
mapOptions?.zoom;
|
|
150
329
|
syncView();
|
|
151
330
|
}
|
|
152
331
|
|
|
153
332
|
$: if (mounted) {
|
|
333
|
+
annotations;
|
|
154
334
|
markers;
|
|
155
|
-
|
|
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();
|
|
156
355
|
}
|
|
157
356
|
|
|
158
357
|
$: if (mounted && isImageMode) {
|
|
@@ -162,13 +361,19 @@
|
|
|
162
361
|
|
|
163
362
|
onMount(() => {
|
|
164
363
|
mounted = true;
|
|
364
|
+
syncSystemColorSchemeSubscription();
|
|
165
365
|
|
|
166
366
|
return () => {
|
|
167
367
|
mounted = false;
|
|
168
368
|
refreshRunId += 1;
|
|
169
369
|
manifestLoadId += 1;
|
|
370
|
+
unsubscribeSystemColorScheme?.();
|
|
170
371
|
imageResizeObserver?.disconnect();
|
|
171
|
-
|
|
372
|
+
try {
|
|
373
|
+
destroyMap();
|
|
374
|
+
} finally {
|
|
375
|
+
interactionCoordinator.dispose();
|
|
376
|
+
}
|
|
172
377
|
};
|
|
173
378
|
});
|
|
174
379
|
|
|
@@ -179,41 +384,41 @@
|
|
|
179
384
|
if (!mounted || runId !== refreshRunId) {
|
|
180
385
|
return;
|
|
181
386
|
}
|
|
182
|
-
|
|
183
387
|
updateImageResizeObserver();
|
|
184
|
-
recreateMap();
|
|
388
|
+
await recreateMap(runId);
|
|
185
389
|
}
|
|
186
390
|
|
|
187
391
|
async function refreshManifest() {
|
|
188
|
-
const shouldLoad =
|
|
189
|
-
|
|
190
|
-
imageMode: isImageMode,
|
|
191
|
-
imageUrl,
|
|
192
|
-
map,
|
|
193
|
-
style: mapStyle,
|
|
194
|
-
styleBaseUrl,
|
|
195
|
-
styleUrl,
|
|
196
|
-
});
|
|
392
|
+
const shouldLoad = shouldLoadManifest;
|
|
393
|
+
const requestKey = manifestRequestKey;
|
|
197
394
|
|
|
198
395
|
if (!shouldLoad) {
|
|
199
396
|
manifestLoadId += 1;
|
|
200
|
-
|
|
397
|
+
loadedManifestMap = null;
|
|
398
|
+
manifestResolutionKey = requestKey;
|
|
399
|
+
manifestResolutionState = 'not-needed';
|
|
201
400
|
return;
|
|
202
401
|
}
|
|
203
402
|
|
|
204
403
|
const loadId = ++manifestLoadId;
|
|
205
|
-
|
|
404
|
+
loadedManifestMap = null;
|
|
405
|
+
manifestResolutionKey = requestKey;
|
|
406
|
+
manifestResolutionState = 'loading';
|
|
206
407
|
|
|
207
408
|
try {
|
|
208
409
|
const manifest = await loadTileflowManifest(manifestUrl);
|
|
209
410
|
|
|
210
411
|
if (mounted && loadId === manifestLoadId) {
|
|
211
|
-
|
|
412
|
+
const resolvedMap =
|
|
413
|
+
manifest && mapName ? resolveTileflowManifestMap(manifest, mapName) : null;
|
|
414
|
+
loadedManifestMap = resolvedMap;
|
|
415
|
+
manifestResolutionState = resolvedMap ? 'ready' : 'error';
|
|
212
416
|
}
|
|
213
417
|
} catch (error) {
|
|
214
418
|
if (mounted && loadId === manifestLoadId) {
|
|
215
419
|
console.error('Failed to load Tileflow manifest', error);
|
|
216
|
-
|
|
420
|
+
loadedManifestMap = null;
|
|
421
|
+
manifestResolutionState = 'error';
|
|
217
422
|
}
|
|
218
423
|
}
|
|
219
424
|
}
|
|
@@ -240,46 +445,150 @@
|
|
|
240
445
|
}
|
|
241
446
|
|
|
242
447
|
function destroyMap() {
|
|
448
|
+
mapLoadId += 1;
|
|
243
449
|
readinessRunId += 1;
|
|
244
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;
|
|
245
462
|
const currentMap = mapInstance;
|
|
463
|
+
const currentThemeController = themeController;
|
|
464
|
+
const worldRequestBridge = mapWorldRequestBridge;
|
|
465
|
+
interactionRuntimesDisposing = true;
|
|
466
|
+
mapFairUseNotice = null;
|
|
246
467
|
mapLifecycleAttachment = null;
|
|
247
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;
|
|
248
490
|
mapResizeObserver?.disconnect();
|
|
249
491
|
mapResizeObserver = null;
|
|
250
492
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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;
|
|
254
514
|
try {
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
|
|
515
|
+
cleanup();
|
|
516
|
+
} catch (error) {
|
|
517
|
+
errors.push(error);
|
|
258
518
|
}
|
|
259
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
|
+
}
|
|
260
527
|
}
|
|
261
528
|
|
|
262
|
-
function recreateMap() {
|
|
529
|
+
async function recreateMap(runId: number) {
|
|
263
530
|
destroyMap();
|
|
264
|
-
|
|
531
|
+
mapCaptureState = 'loading';
|
|
532
|
+
themeTransitionState = 'idle';
|
|
265
533
|
|
|
266
|
-
if (
|
|
534
|
+
if (isImageMode) {
|
|
535
|
+
await markImageLoading();
|
|
267
536
|
return;
|
|
268
537
|
}
|
|
269
538
|
|
|
539
|
+
activeRuntimeResource = runtimeStyle;
|
|
540
|
+
if (!container || !runtimeStyle) {
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const loadId = mapLoadId;
|
|
545
|
+
const targetContainer = container;
|
|
270
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
|
+
|
|
271
572
|
const session = createTileflowSessionController({source: 'svelte'});
|
|
272
573
|
const sessionStarter = createTileflowSessionStarter({
|
|
273
574
|
getSessionId: () => session.sessionId,
|
|
274
575
|
sessionId: session.sessionId,
|
|
275
576
|
source: 'svelte',
|
|
276
577
|
});
|
|
578
|
+
mapFairUseNotice = attachTileflowFairUseNotice(targetContainer);
|
|
579
|
+
registerTileflowContourProtocol({addProtocol: maplibregl.addProtocol});
|
|
580
|
+
mapWorldRequestBridge = registerTileflowWorldRequestBridge({
|
|
581
|
+
addProtocol: maplibregl.addProtocol,
|
|
582
|
+
onNotice: mapFairUseNotice.update,
|
|
583
|
+
});
|
|
277
584
|
const maplibreMap = new maplibregl.Map({
|
|
278
585
|
...mapOptions,
|
|
279
586
|
attributionControl: mapOptions?.attributionControl ?? {compact: true},
|
|
587
|
+
bearing: resolvedBearing,
|
|
280
588
|
center: resolvedCenter as LngLatLike,
|
|
281
|
-
container,
|
|
589
|
+
container: targetContainer,
|
|
282
590
|
interactive: resolvedInteractive,
|
|
591
|
+
pitch: resolvedPitch,
|
|
283
592
|
style: runtime.style as StyleSpecification | string,
|
|
284
593
|
transformRequest: createTileflowTransformRequest({
|
|
285
594
|
always: true,
|
|
@@ -288,11 +597,156 @@
|
|
|
288
597
|
sessionController: session,
|
|
289
598
|
sessionId: session.sessionId,
|
|
290
599
|
transformRequest: mapOptions?.transformRequest ?? undefined,
|
|
600
|
+
worldRequestBridge: mapWorldRequestBridge,
|
|
291
601
|
}),
|
|
292
602
|
zoom: resolvedZoom,
|
|
293
603
|
});
|
|
294
604
|
|
|
295
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();
|
|
296
750
|
|
|
297
751
|
if (resolvedInteractive) {
|
|
298
752
|
maplibreMap.addControl(new maplibregl.NavigationControl(), 'top-right');
|
|
@@ -301,7 +755,7 @@
|
|
|
301
755
|
mapResizeObserver = new ResizeObserver(() => {
|
|
302
756
|
maplibreMap.resize();
|
|
303
757
|
});
|
|
304
|
-
mapResizeObserver.observe(
|
|
758
|
+
mapResizeObserver.observe(targetContainer);
|
|
305
759
|
|
|
306
760
|
mapLifecycleAttachment = attachTileflowMapLifecycle<MapLibreMap, number>({
|
|
307
761
|
getSession: () => {
|
|
@@ -309,7 +763,8 @@
|
|
|
309
763
|
return {
|
|
310
764
|
analytics: analyticsForLoad,
|
|
311
765
|
styleId:
|
|
312
|
-
analyticsForLoad?.styleId ??
|
|
766
|
+
analyticsForLoad?.styleId ??
|
|
767
|
+
(typeof runtime.style === 'string' ? runtime.style : mapName),
|
|
313
768
|
};
|
|
314
769
|
},
|
|
315
770
|
map: maplibreMap,
|
|
@@ -320,31 +775,129 @@
|
|
|
320
775
|
},
|
|
321
776
|
sessionStarter,
|
|
322
777
|
setState: (state) => {
|
|
323
|
-
|
|
778
|
+
mapCaptureState = state;
|
|
324
779
|
},
|
|
325
780
|
subscribe: (targetMap, event, listener) => {
|
|
326
781
|
const subscription = targetMap.on(event, listener);
|
|
327
782
|
return () => subscription.unsubscribe();
|
|
328
783
|
},
|
|
329
784
|
});
|
|
785
|
+
if (themeController && runtimeStyle && runtimeStyle !== runtime) {
|
|
786
|
+
activeRuntimeResource = runtimeStyle;
|
|
787
|
+
void themeController.setTheme(runtimeStyle);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
330
790
|
|
|
331
|
-
|
|
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
|
+
});
|
|
332
814
|
}
|
|
333
815
|
|
|
334
816
|
function syncView() {
|
|
335
817
|
mapInstance?.jumpTo({
|
|
818
|
+
bearing: resolvedBearing,
|
|
336
819
|
center: resolvedCenter as LngLatLike,
|
|
820
|
+
pitch: resolvedPitch,
|
|
337
821
|
zoom: resolvedZoom,
|
|
338
822
|
});
|
|
339
823
|
}
|
|
340
824
|
|
|
341
|
-
function
|
|
342
|
-
|
|
343
|
-
|
|
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
|
+
) {
|
|
344
869
|
return;
|
|
345
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
|
+
}
|
|
346
879
|
|
|
347
|
-
|
|
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
|
+
}
|
|
348
901
|
}
|
|
349
902
|
|
|
350
903
|
async function handleImageLoad(event: Event) {
|
|
@@ -357,22 +910,24 @@
|
|
|
357
910
|
if (typeof image.decode === 'function') await image.decode();
|
|
358
911
|
} catch {
|
|
359
912
|
if (!image.complete || image.naturalWidth === 0) {
|
|
360
|
-
if (runId === readinessRunId)
|
|
913
|
+
if (runId === readinessRunId) mapCaptureState = 'error';
|
|
361
914
|
return;
|
|
362
915
|
}
|
|
363
916
|
}
|
|
364
|
-
if (runId === readinessRunId)
|
|
917
|
+
if (runId === readinessRunId) mapCaptureState = 'idle';
|
|
365
918
|
}
|
|
366
919
|
|
|
367
920
|
async function markImageLoading() {
|
|
368
921
|
const runId = ++readinessRunId;
|
|
369
|
-
|
|
922
|
+
activeRuntimeResource = runtimeImageUrl;
|
|
923
|
+
mapCaptureState = hasInteractionConfiguration ? 'error' : 'loading';
|
|
924
|
+
if (hasInteractionConfiguration) return;
|
|
370
925
|
await tick();
|
|
371
926
|
if (!mounted || runId !== readinessRunId) return;
|
|
372
927
|
const image = container?.querySelector('img');
|
|
373
928
|
if (!image?.complete) return;
|
|
374
929
|
if (image.naturalWidth === 0) {
|
|
375
|
-
|
|
930
|
+
mapCaptureState = 'error';
|
|
376
931
|
return;
|
|
377
932
|
}
|
|
378
933
|
await markImageReady(image);
|
|
@@ -380,19 +935,375 @@
|
|
|
380
935
|
|
|
381
936
|
function handleImageError() {
|
|
382
937
|
readinessRunId += 1;
|
|
383
|
-
|
|
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
|
+
};
|
|
384
1247
|
}
|
|
385
1248
|
|
|
386
1249
|
function formatHeight(value: number | string): string {
|
|
387
1250
|
return typeof value === 'number' ? `${value}px` : value;
|
|
388
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
|
+
}
|
|
389
1299
|
</script>
|
|
390
1300
|
|
|
391
1301
|
<div
|
|
392
1302
|
bind:this={container}
|
|
393
1303
|
class={className}
|
|
394
1304
|
data-tileflow-capture-id={resolvedCaptureId}
|
|
395
|
-
data-tileflow-map={
|
|
1305
|
+
data-tileflow-map={mapName}
|
|
1306
|
+
data-tileflow-theme={resolvedThemeName}
|
|
396
1307
|
data-tileflow-state={captureState}
|
|
397
1308
|
style={frameStyle}
|
|
398
1309
|
>
|
|
@@ -407,4 +1318,30 @@
|
|
|
407
1318
|
style="display: block; height: 100%; object-fit: cover; width: 100%;"
|
|
408
1319
|
/>
|
|
409
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}
|
|
410
1347
|
</div>
|