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