@tileflow/react 0.1.0-alpha.3 → 0.1.0-alpha.30
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/LICENSE +201 -0
- package/README.md +273 -7
- package/dist/index.d.ts +75 -33
- package/dist/index.js +1308 -354
- package/dist/static.d.ts +8 -4
- package/dist/static.js +202 -49
- package/package.json +21 -16
package/dist/index.js
CHANGED
|
@@ -1,156 +1,615 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
1
3
|
// src/map.tsx
|
|
2
4
|
import {
|
|
5
|
+
useCallback,
|
|
3
6
|
useEffect,
|
|
7
|
+
useLayoutEffect,
|
|
4
8
|
useMemo,
|
|
5
9
|
useRef,
|
|
6
10
|
useState
|
|
7
11
|
} from "react";
|
|
8
|
-
import
|
|
12
|
+
import { createPortal } from "react-dom";
|
|
9
13
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
14
|
+
attachTileflowAtmosphere,
|
|
15
|
+
attachTileflowFairUseNotice,
|
|
16
|
+
attachTileflowMapLifecycle,
|
|
17
|
+
createTileflowSessionStarter,
|
|
18
|
+
createTileflowThemeController,
|
|
19
|
+
createTileflowVegetation,
|
|
20
|
+
createTileflowTransformRequest,
|
|
21
|
+
getTileflowSystemColorScheme,
|
|
22
|
+
loadTileflowStyleFonts,
|
|
23
|
+
registerTileflowContourProtocol,
|
|
24
|
+
registerTileflowPmtilesProtocol,
|
|
25
|
+
registerTileflowWorldRequestBridge,
|
|
26
|
+
subscribeTileflowSystemColorScheme
|
|
27
|
+
} from "@tileflow/core/browser";
|
|
28
|
+
import { normalizeTileflowCaptureId } from "@tileflow/core/capture";
|
|
29
|
+
import {
|
|
30
|
+
createTileflowSessionController,
|
|
31
|
+
defaultTileflowManifestUrl,
|
|
32
|
+
defaultTileflowRuntimeView,
|
|
33
|
+
loadTileflowManifest,
|
|
34
|
+
mergeTileflowAnalytics,
|
|
35
|
+
nearestTileflowBlendTheme,
|
|
36
|
+
normalizeTileflowRuntimeCenter,
|
|
37
|
+
normalizeTileflowStaticImageSize,
|
|
38
|
+
resolveTileflowManifestMap,
|
|
39
|
+
resolveTileflowMapMode,
|
|
40
|
+
resolveTileflowRuntimeStyle,
|
|
41
|
+
resolveTileflowRuntimeTheme,
|
|
42
|
+
resolveTileflowRuntimeThemeBlend,
|
|
43
|
+
resolveTileflowRuntimeView,
|
|
44
|
+
resolveTileflowStaticImageUrl
|
|
45
|
+
} from "@tileflow/core/runtime";
|
|
46
|
+
import {
|
|
47
|
+
createTileflowMapLibreDomRuntime,
|
|
48
|
+
createTileflowMapLibreInteractionCoordinator,
|
|
49
|
+
createTileflowMapLibreSemanticDomRuntime
|
|
50
|
+
} from "@tileflow/interactions/maplibre";
|
|
51
|
+
|
|
52
|
+
// src/annotations.ts
|
|
53
|
+
import {
|
|
54
|
+
validateTileflowAnnotations
|
|
55
|
+
} from "@tileflow/interactions";
|
|
56
|
+
function prepareTileflowReactAnnotations(annotations) {
|
|
57
|
+
const candidates = annotations ?? [];
|
|
58
|
+
const titles = new Map(candidates.map((annotation) => [annotation.id, annotation.ariaLabel]));
|
|
59
|
+
const validation = validateTileflowAnnotations(candidates);
|
|
60
|
+
if (!validation.ok) {
|
|
61
|
+
return {
|
|
62
|
+
annotations: [],
|
|
63
|
+
diagnostics: validation.diagnostics,
|
|
64
|
+
ok: false,
|
|
65
|
+
titles
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
// Keep caller-owned identities after validation so generic data and portal reconciliation stay
|
|
70
|
+
// stable instead of replacing application objects with a schema clone.
|
|
71
|
+
annotations: candidates,
|
|
72
|
+
diagnostics: [],
|
|
73
|
+
ok: true,
|
|
74
|
+
titles
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function imageModeAnnotationDiagnostic(annotationCount) {
|
|
78
|
+
return annotationCount > 0 ? {
|
|
79
|
+
code: "UNSUPPORTED_MODE",
|
|
80
|
+
level: "error",
|
|
81
|
+
message: 'Tileflow annotations require mode="interactive"; image mode cannot render interactive overlays.',
|
|
82
|
+
path: ""
|
|
83
|
+
} : void 0;
|
|
84
|
+
}
|
|
85
|
+
function imageModeMapInteractionDiagnostic(annotationCount, bindingCount, hasAdditionalInteractionConfiguration = false) {
|
|
86
|
+
if (bindingCount === 0 && !hasAdditionalInteractionConfiguration) {
|
|
87
|
+
return imageModeAnnotationDiagnostic(annotationCount);
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
code: "UNSUPPORTED_MODE",
|
|
91
|
+
level: "error",
|
|
92
|
+
message: annotationCount > 0 && bindingCount > 0 ? 'Tileflow annotations and interactions require mode="interactive"; image mode cannot render interactive overlays.' : annotationCount > 0 ? 'Tileflow annotations require mode="interactive"; image mode cannot render interactive overlays.' : bindingCount > 0 ? 'Tileflow interactions require mode="interactive"; image mode cannot render interactive overlays.' : 'Tileflow interaction state, callbacks, and renderers require mode="interactive"; image mode cannot render interactive overlays.',
|
|
93
|
+
path: ""
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/interaction-bindings.ts
|
|
98
|
+
import {
|
|
99
|
+
validateTileflowInteractionBindings
|
|
100
|
+
} from "@tileflow/interactions";
|
|
101
|
+
function prepareTileflowReactInteractionBindings(interactions) {
|
|
102
|
+
const validation = validateTileflowInteractionBindings(interactions ?? []);
|
|
103
|
+
return validation.ok ? {
|
|
104
|
+
bindings: validation.bindings,
|
|
105
|
+
diagnostics: [],
|
|
106
|
+
ok: true
|
|
107
|
+
} : {
|
|
108
|
+
diagnostics: validation.diagnostics,
|
|
109
|
+
ok: false
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/interaction-state-input.ts
|
|
114
|
+
import {
|
|
115
|
+
initialTileflowInteractionState,
|
|
116
|
+
tileflowInteractionStateSchema
|
|
117
|
+
} from "@tileflow/interactions";
|
|
118
|
+
function prepareTileflowReactInteractionState(interactionState, defaultInteractionState, controlledOwnership = interactionState !== void 0) {
|
|
119
|
+
const controlled = interactionState !== void 0;
|
|
120
|
+
if (controlled && defaultInteractionState !== void 0) {
|
|
121
|
+
return {
|
|
122
|
+
controlled,
|
|
123
|
+
diagnostics: [
|
|
124
|
+
{
|
|
125
|
+
code: "INVALID_DOCUMENT",
|
|
126
|
+
level: "error",
|
|
127
|
+
message: "Tileflow Map accepts interactionState or defaultInteractionState, not both.",
|
|
128
|
+
path: ""
|
|
129
|
+
}
|
|
130
|
+
],
|
|
131
|
+
ok: false,
|
|
132
|
+
state: initialTileflowInteractionState
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if (controlled !== controlledOwnership) {
|
|
136
|
+
return {
|
|
137
|
+
controlled,
|
|
138
|
+
diagnostics: [
|
|
139
|
+
{
|
|
140
|
+
code: "INVALID_DOCUMENT",
|
|
141
|
+
level: "error",
|
|
142
|
+
message: "Tileflow interaction state ownership cannot switch between controlled and uncontrolled.",
|
|
143
|
+
path: ""
|
|
144
|
+
}
|
|
145
|
+
],
|
|
146
|
+
ok: false,
|
|
147
|
+
state: initialTileflowInteractionState
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
const statePath = controlled ? "/interactionState" : "/defaultInteractionState";
|
|
151
|
+
const parsed = tileflowInteractionStateSchema.safeParse(
|
|
152
|
+
interactionState ?? defaultInteractionState ?? initialTileflowInteractionState
|
|
153
|
+
);
|
|
154
|
+
if (parsed.success) {
|
|
155
|
+
return { controlled, diagnostics: [], ok: true, state: parsed.data };
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
controlled,
|
|
159
|
+
diagnostics: parsed.error.issues.map((issue) => ({
|
|
160
|
+
code: "INVALID_DOCUMENT",
|
|
161
|
+
level: "error",
|
|
162
|
+
message: issue.message,
|
|
163
|
+
path: `${statePath}${issue.path.map((segment) => String(segment).replaceAll("~", "~0").replaceAll("/", "~1")).map((segment) => `/${segment}`).join("")}`
|
|
164
|
+
})),
|
|
165
|
+
ok: false,
|
|
166
|
+
state: initialTileflowInteractionState
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// src/map-style-inputs.ts
|
|
171
|
+
import {
|
|
172
|
+
validateTileflowBrowserThemeBlendSelection
|
|
173
|
+
} from "@tileflow/core/browser";
|
|
174
|
+
import {
|
|
175
|
+
isTileflowTimeOfDay,
|
|
176
|
+
validateTileflowRuntimeSource,
|
|
177
|
+
validateTileflowThemeSelection
|
|
178
|
+
} from "@tileflow/core/runtime";
|
|
179
|
+
function validateTileflowMapStyleInputs(input) {
|
|
180
|
+
const sourceValidation = validateTileflowRuntimeSource(input.source);
|
|
181
|
+
if (!sourceValidation.ok) return sourceValidation;
|
|
182
|
+
if (input.theme !== void 0 && !validateTileflowThemeSelection(input.theme)) {
|
|
183
|
+
return { error: 'theme must be a concrete portable theme name or "system"', ok: false };
|
|
184
|
+
}
|
|
185
|
+
if (input.timeOfDay !== void 0 && !isTileflowTimeOfDay(input.timeOfDay)) {
|
|
186
|
+
return { error: "timeOfDay must be day, dawn, dusk, or night", ok: false };
|
|
187
|
+
}
|
|
188
|
+
if (input.themeBlend !== void 0 && !validateTileflowBrowserThemeBlendSelection(input.themeBlend)) {
|
|
189
|
+
return {
|
|
190
|
+
error: "themeBlend must name two to eight concrete themes, a position from 0 to themes.length - 1, and an optional boolean hold",
|
|
191
|
+
ok: false
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
if (input.themeTransition !== void 0 && !isThemeTransition(input.themeTransition)) {
|
|
195
|
+
return {
|
|
196
|
+
error: "themeTransition.duration must be a number of milliseconds from 0 to 5000",
|
|
197
|
+
ok: false
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
return { ok: true };
|
|
201
|
+
}
|
|
202
|
+
function isThemeTransition(value) {
|
|
203
|
+
if (!value || typeof value !== "object") return false;
|
|
204
|
+
const { duration } = value;
|
|
205
|
+
return duration === void 0 || typeof duration === "number" && Number.isFinite(duration) && duration >= 0 && duration <= 5e3;
|
|
206
|
+
}
|
|
207
|
+
function assertTileflowMapStyleInputs(input) {
|
|
208
|
+
const validation = validateTileflowMapStyleInputs(input);
|
|
209
|
+
if (!validation.ok) {
|
|
210
|
+
throw new TypeError(`Invalid Tileflow <Map> source: ${validation.error}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// src/maplibre.ts
|
|
215
|
+
var mapLibrePromise;
|
|
216
|
+
var mapLibre;
|
|
217
|
+
var workerUrl;
|
|
218
|
+
function configureTileflowMapLibre(configuration) {
|
|
219
|
+
const nextWorkerUrl = configuration.workerUrl;
|
|
220
|
+
if (typeof nextWorkerUrl !== "string" || nextWorkerUrl.trim().length === 0) {
|
|
221
|
+
throw new TypeError("Tileflow MapLibre workerUrl must be a non-empty string.");
|
|
222
|
+
}
|
|
223
|
+
if (workerUrl !== void 0 && workerUrl !== nextWorkerUrl) {
|
|
224
|
+
throw new Error("Tileflow MapLibre workerUrl is already configured.");
|
|
225
|
+
}
|
|
226
|
+
workerUrl = nextWorkerUrl;
|
|
227
|
+
mapLibre?.setWorkerUrl(nextWorkerUrl);
|
|
228
|
+
}
|
|
229
|
+
function loadTileflowMapLibre() {
|
|
230
|
+
mapLibrePromise ??= import("maplibre-gl").then(resolveMapLibreModule).then(configureTileflowMapLibreModule).catch((error) => {
|
|
231
|
+
mapLibrePromise = void 0;
|
|
232
|
+
throw error;
|
|
233
|
+
});
|
|
234
|
+
return mapLibrePromise;
|
|
235
|
+
}
|
|
236
|
+
function resolveMapLibreModule(module) {
|
|
237
|
+
const defaultExport = module.default;
|
|
238
|
+
return defaultExport ?? module;
|
|
239
|
+
}
|
|
240
|
+
function configureTileflowMapLibreModule(maplibregl) {
|
|
241
|
+
if (workerUrl !== void 0) maplibregl.setWorkerUrl(workerUrl);
|
|
242
|
+
mapLibre = maplibregl;
|
|
243
|
+
return maplibregl;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// src/map.tsx
|
|
247
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
248
|
+
var defaultInteractions = [];
|
|
249
|
+
function Map2(props) {
|
|
250
|
+
assertTileflowMapStyleInputs(props);
|
|
251
|
+
const {
|
|
252
|
+
annotations,
|
|
253
|
+
captureId,
|
|
254
|
+
defaultInteractionState,
|
|
255
|
+
mode = "interactive",
|
|
256
|
+
source,
|
|
257
|
+
imageUrl,
|
|
258
|
+
alt = "",
|
|
259
|
+
center,
|
|
260
|
+
zoom,
|
|
261
|
+
className,
|
|
262
|
+
height = 420,
|
|
263
|
+
imageLoading = "eager",
|
|
264
|
+
interactive,
|
|
265
|
+
mapOptions,
|
|
266
|
+
analytics,
|
|
267
|
+
theme,
|
|
268
|
+
timeOfDay,
|
|
269
|
+
themeBlend,
|
|
270
|
+
themeTransition,
|
|
271
|
+
vegetation,
|
|
272
|
+
viewMode = "2d",
|
|
273
|
+
interactionState,
|
|
274
|
+
interactions = defaultInteractions,
|
|
275
|
+
onInteractionDiagnostic,
|
|
276
|
+
onInteractionEvent,
|
|
277
|
+
onInteractionStateChange,
|
|
278
|
+
onLoad,
|
|
279
|
+
renderMarker,
|
|
280
|
+
renderPopup,
|
|
281
|
+
renderTooltip
|
|
282
|
+
} = props;
|
|
283
|
+
const generalRenderPopup = renderPopup;
|
|
284
|
+
const generalRenderTooltip = renderTooltip;
|
|
285
|
+
const sourceMap = source.map;
|
|
286
|
+
const sourceManifestUrl = source.manifestUrl;
|
|
287
|
+
const runtimeSource = useMemo(
|
|
288
|
+
() => ({ manifestUrl: sourceManifestUrl, map: sourceMap }),
|
|
289
|
+
[sourceManifestUrl, sourceMap]
|
|
290
|
+
);
|
|
291
|
+
const mapName = runtimeSource.map;
|
|
292
|
+
const manifestUrl = runtimeSource.manifestUrl ?? defaultTileflowManifestUrl;
|
|
293
|
+
const preparedAnnotations = useMemo(
|
|
294
|
+
() => prepareTileflowReactAnnotations(annotations),
|
|
295
|
+
[annotations]
|
|
296
|
+
);
|
|
297
|
+
const preparedInteractions = useMemo(
|
|
298
|
+
() => prepareTileflowReactInteractionBindings(interactions),
|
|
299
|
+
[interactions]
|
|
300
|
+
);
|
|
301
|
+
const controlledInteractionOwnershipRef = useRef(interactionState !== void 0);
|
|
302
|
+
const preparedInteractionState = useMemo(
|
|
303
|
+
() => prepareTileflowReactInteractionState(
|
|
304
|
+
interactionState,
|
|
305
|
+
defaultInteractionState,
|
|
306
|
+
controlledInteractionOwnershipRef.current
|
|
307
|
+
),
|
|
308
|
+
[defaultInteractionState, interactionState]
|
|
309
|
+
);
|
|
42
310
|
const containerRef = useRef(null);
|
|
311
|
+
const imageRef = useRef(null);
|
|
43
312
|
const mapRef = useRef(null);
|
|
44
|
-
const
|
|
45
|
-
const
|
|
313
|
+
const vegetationRef = useRef(void 0);
|
|
314
|
+
const vegetationInputsRef = useRef({ vegetation, viewMode });
|
|
315
|
+
vegetationInputsRef.current = { vegetation, viewMode };
|
|
316
|
+
useEffect(() => {
|
|
317
|
+
vegetationRef.current?.setVisible(vegetation);
|
|
318
|
+
vegetationRef.current?.setViewMode(viewMode);
|
|
319
|
+
}, [vegetation, viewMode]);
|
|
320
|
+
const themeControllerRef = useRef(void 0);
|
|
321
|
+
const runtimeStyleRef = useRef(null);
|
|
322
|
+
const interactionRuntimeRef = useRef(
|
|
323
|
+
void 0
|
|
324
|
+
);
|
|
325
|
+
const semanticRuntimeRef = useRef(void 0);
|
|
326
|
+
const interactionCoordinatorRef = useRef(
|
|
327
|
+
void 0
|
|
328
|
+
);
|
|
329
|
+
const annotationsRef = useRef(preparedAnnotations.annotations);
|
|
330
|
+
const annotationTitlesRef = useRef(preparedAnnotations.titles);
|
|
331
|
+
const preparedInteractionsRef = useRef(preparedInteractions);
|
|
332
|
+
const coordinatedInteractionStateRef = useRef(preparedInteractionState.state);
|
|
333
|
+
const onInteractionDiagnosticRef = useRef(onInteractionDiagnostic);
|
|
334
|
+
const onInteractionEventRef = useRef(onInteractionEvent);
|
|
335
|
+
const onInteractionStateChangeRef = useRef(onInteractionStateChange);
|
|
336
|
+
const onThemeChangeRef = useRef(props.onThemeChange);
|
|
337
|
+
const renderMarkerRef = useRef(renderMarker);
|
|
338
|
+
const renderPopupRef = useRef(renderPopup);
|
|
339
|
+
const renderTooltipRef = useRef(renderTooltip);
|
|
340
|
+
const reportedDeclarativeDiagnosticKeysRef = useRef(/* @__PURE__ */ new Set());
|
|
341
|
+
const [manifestResolution, setManifestResolution] = useState({
|
|
342
|
+
key: "",
|
|
343
|
+
map: null,
|
|
344
|
+
state: "loading"
|
|
345
|
+
});
|
|
346
|
+
const [systemColorScheme, setSystemColorScheme] = useState("light");
|
|
46
347
|
const [imageSize, setImageSize] = useState(null);
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
348
|
+
const [captureState, setCaptureState] = useState("loading");
|
|
349
|
+
const [themeTransitionState, setThemeTransitionState] = useState(
|
|
350
|
+
"idle"
|
|
351
|
+
);
|
|
352
|
+
const [activeRuntimeResource, setActiveRuntimeResource] = useState(void 0);
|
|
353
|
+
const [annotationRenderTargets, setAnnotationRenderTargets] = useState([]);
|
|
354
|
+
const [semanticRenderTargets, setSemanticRenderTargets] = useState([]);
|
|
355
|
+
const [annotationBridgeDiagnostic, setAnnotationBridgeDiagnostic] = useState();
|
|
356
|
+
const [semanticBridgeDiagnostic, setSemanticBridgeDiagnostic] = useState();
|
|
357
|
+
const [stateBridgeDiagnostic, setStateBridgeDiagnostic] = useState();
|
|
358
|
+
const [rendererBridgeDiagnostic, setRendererBridgeDiagnostic] = useState();
|
|
359
|
+
const [annotationDiagnostics, setAnnotationDiagnostics] = useState([]);
|
|
360
|
+
const [semanticDiagnostics, setSemanticDiagnostics] = useState([]);
|
|
361
|
+
const stableMapOptions = useStableMapOptions(mapOptions);
|
|
362
|
+
const resolvedCaptureId = normalizeTileflowCaptureId(captureId);
|
|
363
|
+
const resolvedMode = resolveTileflowMapMode({ mode });
|
|
364
|
+
const isImageMode = resolvedMode === "image";
|
|
365
|
+
const manifestRequestKey = JSON.stringify([manifestUrl, mapName]);
|
|
366
|
+
const currentManifestResolution = manifestResolution.key === manifestRequestKey ? manifestResolution : { key: manifestRequestKey, map: null, state: "loading" };
|
|
367
|
+
const manifestMap = currentManifestResolution.state === "ready" ? currentManifestResolution.map : null;
|
|
368
|
+
const themeBlendKey = themeBlend ? JSON.stringify(themeBlend.themes) : void 0;
|
|
369
|
+
const themeBlendPosition = themeBlend?.position;
|
|
370
|
+
const themeBlendHold = themeBlend?.hold === true;
|
|
371
|
+
const blendThemeName = themeBlend ? nearestTileflowBlendTheme(themeBlend) : void 0;
|
|
372
|
+
const themeResolution = useMemo(() => {
|
|
373
|
+
const selection = blendThemeName ?? theme;
|
|
374
|
+
if (!manifestMap) {
|
|
375
|
+
return {
|
|
376
|
+
error: false,
|
|
377
|
+
name: selection && selection !== "system" ? selection : void 0
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
try {
|
|
381
|
+
if (themeBlendKey) {
|
|
382
|
+
for (const name of JSON.parse(themeBlendKey)) {
|
|
383
|
+
resolveTileflowRuntimeTheme(manifestMap, name);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
error: false,
|
|
388
|
+
name: resolveTileflowRuntimeTheme(
|
|
389
|
+
manifestMap,
|
|
390
|
+
selection,
|
|
391
|
+
systemColorScheme,
|
|
392
|
+
themeBlendKey ? void 0 : timeOfDay
|
|
393
|
+
).name
|
|
394
|
+
};
|
|
395
|
+
} catch {
|
|
396
|
+
return { error: true, name: void 0 };
|
|
397
|
+
}
|
|
398
|
+
}, [blendThemeName, manifestMap, systemColorScheme, theme, themeBlendKey, timeOfDay]);
|
|
399
|
+
const resolvedThemeName = themeResolution.name;
|
|
400
|
+
const manifestView = resolveTileflowRuntimeView({ manifestMap });
|
|
401
|
+
const manifestCenter = useStableMapOptionValue(
|
|
402
|
+
normalizeTileflowRuntimeCenter(manifestView.center)
|
|
403
|
+
);
|
|
404
|
+
const resolvedCenter = useStableMapOptionValue(
|
|
405
|
+
center ?? stableMapOptions?.center ?? manifestCenter
|
|
51
406
|
);
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
407
|
+
const resolvedZoom = zoom ?? stableMapOptions?.zoom ?? manifestView.zoom ?? defaultTileflowRuntimeView.zoom;
|
|
408
|
+
const resolvedBearing = stableMapOptions?.bearing ?? manifestView.bearing;
|
|
409
|
+
const resolvedPitch = stableMapOptions?.pitch ?? manifestView.pitch;
|
|
410
|
+
const resolvedInteractive = interactive ?? stableMapOptions?.interactive ?? true;
|
|
411
|
+
const staticImageCenter = useStableMapOptionValue(
|
|
412
|
+
normalizeTileflowRuntimeCenter(center ?? stableMapOptions?.center, manifestCenter)
|
|
413
|
+
);
|
|
414
|
+
const staticImageZoom = zoom ?? stableMapOptions?.zoom ?? manifestView.zoom ?? defaultTileflowRuntimeView.zoom;
|
|
415
|
+
const centerRef = useRef(resolvedCenter);
|
|
416
|
+
const zoomRef = useRef(resolvedZoom);
|
|
417
|
+
const bearingRef = useRef(resolvedBearing);
|
|
418
|
+
const pitchRef = useRef(resolvedPitch);
|
|
419
|
+
const onLoadRef = useRef(onLoad);
|
|
420
|
+
const mapNameRef = useRef(mapName);
|
|
421
|
+
const resolvedAnalyticsRef = useRef(void 0);
|
|
422
|
+
const readinessRunRef = useRef(0);
|
|
423
|
+
const hasAdditionalInteractionConfiguration = annotations !== void 0 || defaultInteractionState !== void 0 || interactionState !== void 0 || onInteractionDiagnostic !== void 0 || onInteractionEvent !== void 0 || onInteractionStateChange !== void 0 || renderMarker !== void 0 || renderPopup !== void 0 || renderTooltip !== void 0 || props.interactions !== void 0;
|
|
424
|
+
const modeDiagnostic = useMemo(
|
|
425
|
+
() => isImageMode ? imageModeMapInteractionDiagnostic(
|
|
426
|
+
preparedAnnotations.annotations.length,
|
|
427
|
+
interactions.length,
|
|
428
|
+
hasAdditionalInteractionConfiguration
|
|
429
|
+
) : void 0,
|
|
430
|
+
[
|
|
431
|
+
hasAdditionalInteractionConfiguration,
|
|
432
|
+
interactions.length,
|
|
433
|
+
isImageMode,
|
|
434
|
+
preparedAnnotations.annotations.length
|
|
435
|
+
]
|
|
436
|
+
);
|
|
437
|
+
const declarativeInteractionDiagnostics = useMemo(
|
|
438
|
+
() => [
|
|
439
|
+
modeDiagnostic,
|
|
440
|
+
...preparedAnnotations.diagnostics,
|
|
441
|
+
...preparedInteractions.diagnostics,
|
|
442
|
+
...preparedInteractionState.diagnostics
|
|
443
|
+
].filter(
|
|
444
|
+
(diagnostic) => diagnostic !== void 0
|
|
55
445
|
),
|
|
56
|
-
[
|
|
446
|
+
[
|
|
447
|
+
modeDiagnostic,
|
|
448
|
+
preparedAnnotations.diagnostics,
|
|
449
|
+
preparedInteractions.diagnostics,
|
|
450
|
+
preparedInteractionState.diagnostics
|
|
451
|
+
]
|
|
452
|
+
);
|
|
453
|
+
const interactionDiagnostics = [
|
|
454
|
+
...declarativeInteractionDiagnostics,
|
|
455
|
+
...annotationBridgeDiagnostic ? [annotationBridgeDiagnostic] : [],
|
|
456
|
+
...semanticBridgeDiagnostic ? [semanticBridgeDiagnostic] : [],
|
|
457
|
+
...stateBridgeDiagnostic ? [stateBridgeDiagnostic] : [],
|
|
458
|
+
...rendererBridgeDiagnostic ? [rendererBridgeDiagnostic] : [],
|
|
459
|
+
...annotationDiagnostics,
|
|
460
|
+
...semanticDiagnostics
|
|
461
|
+
];
|
|
462
|
+
const interactionDiagnostic = interactionDiagnostics.find((diagnostic) => diagnostic.level === "error") ?? interactionDiagnostics[0];
|
|
463
|
+
const hasInteractionError = interactionDiagnostics.some(
|
|
464
|
+
(diagnostic) => diagnostic.level === "error"
|
|
465
|
+
);
|
|
466
|
+
const portalKeys = useMemo(
|
|
467
|
+
() => [
|
|
468
|
+
...annotationRenderTargets.map((target) => `annotation:${target.key}`),
|
|
469
|
+
...semanticRenderTargets.map((target) => `semantic:${target.key}`)
|
|
470
|
+
],
|
|
471
|
+
[annotationRenderTargets, semanticRenderTargets]
|
|
57
472
|
);
|
|
473
|
+
const portalReadiness = useTileflowPortalReadiness(portalKeys);
|
|
474
|
+
const interactionReadiness = hasInteractionError ? "error" : portalReadiness.idle ? "idle" : "loading";
|
|
475
|
+
if (preparedAnnotations.ok) {
|
|
476
|
+
annotationsRef.current = preparedAnnotations.annotations;
|
|
477
|
+
annotationTitlesRef.current = preparedAnnotations.titles;
|
|
478
|
+
}
|
|
479
|
+
preparedInteractionsRef.current = preparedInteractions;
|
|
480
|
+
onInteractionDiagnosticRef.current = onInteractionDiagnostic;
|
|
481
|
+
onInteractionEventRef.current = onInteractionEvent;
|
|
482
|
+
onInteractionStateChangeRef.current = onInteractionStateChange;
|
|
483
|
+
renderMarkerRef.current = renderMarker;
|
|
484
|
+
renderPopupRef.current = renderPopup;
|
|
485
|
+
renderTooltipRef.current = renderTooltip;
|
|
486
|
+
onThemeChangeRef.current = props.onThemeChange;
|
|
58
487
|
useEffect(() => {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
488
|
+
if (theme !== "system") return;
|
|
489
|
+
setSystemColorScheme(getTileflowSystemColorScheme());
|
|
490
|
+
return subscribeTileflowSystemColorScheme(setSystemColorScheme);
|
|
491
|
+
}, [theme]);
|
|
492
|
+
useEffect(() => {
|
|
493
|
+
const previousKeys = reportedDeclarativeDiagnosticKeysRef.current;
|
|
494
|
+
const nextKeys = /* @__PURE__ */ new Set();
|
|
495
|
+
for (const diagnostic of declarativeInteractionDiagnostics) {
|
|
496
|
+
const key = tileflowInteractionDiagnosticKey(diagnostic);
|
|
497
|
+
nextKeys.add(key);
|
|
498
|
+
if (!previousKeys.has(key)) onInteractionDiagnosticRef.current?.(diagnostic);
|
|
65
499
|
}
|
|
500
|
+
reportedDeclarativeDiagnosticKeysRef.current = nextKeys;
|
|
501
|
+
}, [declarativeInteractionDiagnostics]);
|
|
502
|
+
useEffect(() => {
|
|
66
503
|
let cancelled = false;
|
|
67
|
-
|
|
504
|
+
setManifestResolution({ key: manifestRequestKey, map: null, state: "loading" });
|
|
505
|
+
loadTileflowManifest(manifestUrl).then((manifest) => {
|
|
68
506
|
if (cancelled) {
|
|
69
507
|
return;
|
|
70
508
|
}
|
|
71
|
-
|
|
509
|
+
const resolvedMap = manifest && mapName ? resolveTileflowManifestMap(manifest, mapName) : null;
|
|
510
|
+
setManifestResolution({
|
|
511
|
+
key: manifestRequestKey,
|
|
512
|
+
map: resolvedMap,
|
|
513
|
+
state: resolvedMap ? "ready" : "error"
|
|
514
|
+
});
|
|
72
515
|
}).catch((error) => {
|
|
73
516
|
if (!cancelled) {
|
|
74
517
|
console.error("Failed to load Tileflow manifest", error);
|
|
75
|
-
|
|
518
|
+
setManifestResolution({ key: manifestRequestKey, map: null, state: "error" });
|
|
76
519
|
}
|
|
77
520
|
});
|
|
78
521
|
return () => {
|
|
79
522
|
cancelled = true;
|
|
80
523
|
};
|
|
81
|
-
}, [
|
|
82
|
-
config,
|
|
83
|
-
imageUrl,
|
|
84
|
-
isImageMode,
|
|
85
|
-
manifestUrl,
|
|
86
|
-
mapName,
|
|
87
|
-
style,
|
|
88
|
-
styleUrl,
|
|
89
|
-
useExplicitStyleBaseUrl
|
|
90
|
-
]);
|
|
524
|
+
}, [manifestRequestKey, manifestUrl, mapName]);
|
|
91
525
|
const runtimeStyle = useMemo(() => {
|
|
92
|
-
if (isImageMode) {
|
|
526
|
+
if (isImageMode || themeResolution.error) {
|
|
93
527
|
return null;
|
|
94
528
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
mapId: manifestMap.mapId,
|
|
113
|
-
styleId: manifestMap.styleId
|
|
114
|
-
},
|
|
115
|
-
style: manifestMap.styleUrl
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
if (useLocalDevFallback) {
|
|
119
|
-
return { style: resolveStyleUrl(mapName) };
|
|
120
|
-
}
|
|
529
|
+
return resolveTileflowRuntimeStyle({
|
|
530
|
+
colorScheme: systemColorScheme,
|
|
531
|
+
manifestMap,
|
|
532
|
+
source: runtimeSource,
|
|
533
|
+
theme: themeResolution.name
|
|
534
|
+
});
|
|
535
|
+
}, [isImageMode, manifestMap, runtimeSource, systemColorScheme, themeResolution]);
|
|
536
|
+
runtimeStyleRef.current = runtimeStyle;
|
|
537
|
+
const blendStyles = useMemo(() => {
|
|
538
|
+
if (isImageMode || !manifestMap || !themeBlendKey) return null;
|
|
539
|
+
try {
|
|
540
|
+
return resolveTileflowRuntimeThemeBlend({
|
|
541
|
+
blend: { position: 0, themes: JSON.parse(themeBlendKey) },
|
|
542
|
+
manifestMap,
|
|
543
|
+
source: runtimeSource
|
|
544
|
+
});
|
|
545
|
+
} catch {
|
|
121
546
|
return null;
|
|
122
547
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
styleBaseUrl,
|
|
131
|
-
styleUrl,
|
|
132
|
-
themes,
|
|
133
|
-
tileBaseUrl,
|
|
134
|
-
useExplicitStyleBaseUrl,
|
|
135
|
-
useLocalDevFallback
|
|
136
|
-
]);
|
|
548
|
+
}, [isImageMode, manifestMap, runtimeSource, themeBlendKey]);
|
|
549
|
+
const blendRequestRef = useRef(null);
|
|
550
|
+
blendRequestRef.current = blendStyles && themeBlendPosition !== void 0 ? { hold: themeBlendHold, position: themeBlendPosition, themes: blendStyles } : null;
|
|
551
|
+
const themeTransitionRef = useRef(themeTransition);
|
|
552
|
+
themeTransitionRef.current = themeTransition;
|
|
553
|
+
const runtimeMapIdentity = `${manifestUrl}\0${runtimeSource.map}`;
|
|
554
|
+
const runtimeStyleReady = runtimeStyle !== null || themeResolution.error && mapRef.current !== null;
|
|
137
555
|
const resolvedAnalytics = useMemo(
|
|
138
|
-
() =>
|
|
556
|
+
() => mergeTileflowAnalytics(analytics, runtimeStyle?.analytics),
|
|
139
557
|
[analytics, runtimeStyle]
|
|
140
558
|
);
|
|
559
|
+
useEffect(() => {
|
|
560
|
+
centerRef.current = resolvedCenter;
|
|
561
|
+
zoomRef.current = resolvedZoom;
|
|
562
|
+
bearingRef.current = resolvedBearing;
|
|
563
|
+
pitchRef.current = resolvedPitch;
|
|
564
|
+
mapRef.current?.jumpTo({
|
|
565
|
+
bearing: resolvedBearing,
|
|
566
|
+
center: resolvedCenter,
|
|
567
|
+
pitch: resolvedPitch,
|
|
568
|
+
zoom: resolvedZoom
|
|
569
|
+
});
|
|
570
|
+
}, [resolvedBearing, resolvedCenter, resolvedPitch, resolvedZoom]);
|
|
571
|
+
useEffect(() => {
|
|
572
|
+
onLoadRef.current = onLoad;
|
|
573
|
+
}, [onLoad]);
|
|
574
|
+
useEffect(() => {
|
|
575
|
+
mapNameRef.current = mapName;
|
|
576
|
+
}, [mapName]);
|
|
577
|
+
useEffect(() => {
|
|
578
|
+
resolvedAnalyticsRef.current = resolvedAnalytics;
|
|
579
|
+
}, [resolvedAnalytics]);
|
|
141
580
|
const runtimeImageUrl = useMemo(
|
|
142
|
-
() => imageUrl ?? (isImageMode ?
|
|
143
|
-
center,
|
|
581
|
+
() => imageUrl ?? (isImageMode && !themeResolution.error ? resolveTileflowStaticImageUrl({
|
|
582
|
+
center: staticImageCenter,
|
|
583
|
+
colorScheme: systemColorScheme,
|
|
144
584
|
imageSize,
|
|
145
585
|
manifestMap,
|
|
146
|
-
|
|
586
|
+
theme: themeResolution.name,
|
|
587
|
+
zoom: staticImageZoom
|
|
147
588
|
}) : void 0),
|
|
148
|
-
[
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
589
|
+
[
|
|
590
|
+
imageSize,
|
|
591
|
+
imageUrl,
|
|
592
|
+
isImageMode,
|
|
593
|
+
manifestMap,
|
|
594
|
+
staticImageCenter,
|
|
595
|
+
staticImageZoom,
|
|
596
|
+
systemColorScheme,
|
|
597
|
+
themeResolution
|
|
598
|
+
]
|
|
153
599
|
);
|
|
600
|
+
const runtimeResolutionState = (() => {
|
|
601
|
+
if (currentManifestResolution.state !== "ready") {
|
|
602
|
+
return currentManifestResolution.state === "error" ? "error" : "loading";
|
|
603
|
+
}
|
|
604
|
+
if (themeResolution.error) return "error";
|
|
605
|
+
if (isImageMode) {
|
|
606
|
+
if (runtimeImageUrl) return "idle";
|
|
607
|
+
return imageUrl === void 0 && imageSize === null ? "loading" : "error";
|
|
608
|
+
}
|
|
609
|
+
return runtimeStyle ? "idle" : "error";
|
|
610
|
+
})();
|
|
611
|
+
const currentCaptureState = activeRuntimeResource === (isImageMode ? runtimeImageUrl : runtimeStyle) ? captureState : "loading";
|
|
612
|
+
const effectiveCaptureState = currentCaptureState === "error" || interactionReadiness === "error" || runtimeResolutionState === "error" || themeTransitionState === "error" ? "error" : currentCaptureState === "idle" && interactionReadiness === "idle" && runtimeResolutionState === "idle" && themeTransitionState === "idle" ? "idle" : "loading";
|
|
154
613
|
useEffect(() => {
|
|
155
614
|
if (!isImageMode || imageUrl || !containerRef.current) {
|
|
156
615
|
setImageSize(null);
|
|
@@ -158,7 +617,7 @@ function Map({
|
|
|
158
617
|
}
|
|
159
618
|
const element = containerRef.current;
|
|
160
619
|
const updateImageSize = () => {
|
|
161
|
-
const nextSize =
|
|
620
|
+
const nextSize = normalizeTileflowStaticImageSize({
|
|
162
621
|
height: element.clientHeight,
|
|
163
622
|
width: element.clientWidth
|
|
164
623
|
});
|
|
@@ -174,60 +633,463 @@ function Map({
|
|
|
174
633
|
};
|
|
175
634
|
}, [imageUrl, isImageMode]);
|
|
176
635
|
useEffect(() => {
|
|
177
|
-
|
|
178
|
-
|
|
636
|
+
const runtime = interactionRuntimeRef.current;
|
|
637
|
+
if (!runtime || !preparedAnnotations.ok) return;
|
|
638
|
+
try {
|
|
639
|
+
setAnnotationBridgeDiagnostic(void 0);
|
|
640
|
+
runtime.reconcile(preparedAnnotations.annotations);
|
|
641
|
+
} catch (error) {
|
|
642
|
+
console.error("Failed to reconcile Tileflow annotations", error);
|
|
643
|
+
const diagnostic = createOverlayFailureDiagnostic(
|
|
644
|
+
"Tileflow annotations could not be reconciled."
|
|
645
|
+
);
|
|
646
|
+
setAnnotationBridgeDiagnostic(diagnostic);
|
|
647
|
+
onInteractionDiagnosticRef.current?.(diagnostic);
|
|
179
648
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
649
|
+
}, [preparedAnnotations]);
|
|
650
|
+
useEffect(() => {
|
|
651
|
+
const runtime = semanticRuntimeRef.current;
|
|
652
|
+
if (!runtime || !preparedInteractions.ok) return;
|
|
653
|
+
try {
|
|
654
|
+
setSemanticBridgeDiagnostic(void 0);
|
|
655
|
+
runtime.reconcile(preparedInteractions.bindings);
|
|
656
|
+
} catch (error) {
|
|
657
|
+
console.error("Failed to reconcile Tileflow semantic interactions", error);
|
|
658
|
+
const diagnostic = createOverlayFailureDiagnostic(
|
|
659
|
+
"Tileflow semantic interactions could not be reconciled."
|
|
660
|
+
);
|
|
661
|
+
setSemanticBridgeDiagnostic(diagnostic);
|
|
662
|
+
onInteractionDiagnosticRef.current?.(diagnostic);
|
|
193
663
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
664
|
+
}, [preparedInteractions]);
|
|
665
|
+
useEffect(() => {
|
|
666
|
+
if (!preparedInteractionState.controlled || !preparedInteractionState.ok) return;
|
|
667
|
+
try {
|
|
668
|
+
setStateBridgeDiagnostic(void 0);
|
|
669
|
+
interactionCoordinatorRef.current?.setInteractionState(preparedInteractionState.state);
|
|
670
|
+
coordinatedInteractionStateRef.current = preparedInteractionState.state;
|
|
671
|
+
} catch (error) {
|
|
672
|
+
console.error("Failed to update Tileflow interaction state", error);
|
|
673
|
+
const diagnostic = createOverlayFailureDiagnostic(
|
|
674
|
+
"Tileflow interaction state could not be updated."
|
|
675
|
+
);
|
|
676
|
+
setStateBridgeDiagnostic(diagnostic);
|
|
677
|
+
onInteractionDiagnosticRef.current?.(diagnostic);
|
|
678
|
+
}
|
|
679
|
+
}, [preparedInteractionState]);
|
|
680
|
+
useEffect(() => {
|
|
681
|
+
const annotationRuntime = interactionRuntimeRef.current;
|
|
682
|
+
const semanticRuntime = semanticRuntimeRef.current;
|
|
683
|
+
if (!annotationRuntime && !semanticRuntime) return;
|
|
684
|
+
try {
|
|
685
|
+
setRendererBridgeDiagnostic(void 0);
|
|
686
|
+
annotationRuntime?.setCustomRenderers({
|
|
687
|
+
marker: renderMarker !== void 0,
|
|
688
|
+
popup: renderPopup !== void 0,
|
|
689
|
+
tooltip: renderTooltip !== void 0
|
|
211
690
|
});
|
|
691
|
+
semanticRuntime?.setCustomRenderers({
|
|
692
|
+
popup: renderPopup !== void 0,
|
|
693
|
+
tooltip: renderTooltip !== void 0
|
|
694
|
+
});
|
|
695
|
+
} catch (error) {
|
|
696
|
+
console.error("Failed to update Tileflow interaction renderers", error);
|
|
697
|
+
const diagnostic = createOverlayFailureDiagnostic(
|
|
698
|
+
"Tileflow interaction renderers could not be updated."
|
|
699
|
+
);
|
|
700
|
+
setRendererBridgeDiagnostic(diagnostic);
|
|
701
|
+
onInteractionDiagnosticRef.current?.(diagnostic);
|
|
702
|
+
}
|
|
703
|
+
}, [renderMarker, renderPopup, renderTooltip]);
|
|
704
|
+
useEffect(() => {
|
|
705
|
+
setThemeTransitionState("idle");
|
|
706
|
+
const initialRuntimeStyle = runtimeStyleRef.current;
|
|
707
|
+
if (!containerRef.current || !initialRuntimeStyle) {
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
readinessRunRef.current += 1;
|
|
711
|
+
setActiveRuntimeResource(initialRuntimeStyle);
|
|
712
|
+
setCaptureState("loading");
|
|
713
|
+
const container = containerRef.current;
|
|
714
|
+
let cancelled = false;
|
|
715
|
+
let disposeMap;
|
|
716
|
+
void Promise.all([
|
|
717
|
+
loadTileflowMapLibre(),
|
|
718
|
+
loadTileflowStyleFonts(initialRuntimeStyle.style, {
|
|
719
|
+
fontFaces: initialRuntimeStyle.fontFaces
|
|
720
|
+
})
|
|
721
|
+
]).then(([maplibregl]) => {
|
|
722
|
+
if (cancelled || containerRef.current !== container) {
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
const setupCleanups = [];
|
|
726
|
+
let setupDisposed = false;
|
|
727
|
+
const setupResources = {};
|
|
728
|
+
const registerCleanup = (cleanup) => setupCleanups.push(cleanup);
|
|
729
|
+
disposeMap = () => {
|
|
730
|
+
if (setupDisposed) return;
|
|
731
|
+
setupDisposed = true;
|
|
732
|
+
try {
|
|
733
|
+
runTileflowReactCleanups([...setupCleanups].reverse());
|
|
734
|
+
} finally {
|
|
735
|
+
if (mapRef.current === setupResources.map) mapRef.current = null;
|
|
736
|
+
if (interactionRuntimeRef.current === setupResources.annotationRuntime) {
|
|
737
|
+
interactionRuntimeRef.current = void 0;
|
|
738
|
+
}
|
|
739
|
+
if (semanticRuntimeRef.current === setupResources.semanticRuntime) {
|
|
740
|
+
semanticRuntimeRef.current = void 0;
|
|
741
|
+
}
|
|
742
|
+
if (interactionCoordinatorRef.current === setupResources.coordinator) {
|
|
743
|
+
interactionCoordinatorRef.current = void 0;
|
|
744
|
+
}
|
|
745
|
+
setAnnotationDiagnostics([]);
|
|
746
|
+
setAnnotationRenderTargets([]);
|
|
747
|
+
setAnnotationBridgeDiagnostic(void 0);
|
|
748
|
+
setSemanticDiagnostics([]);
|
|
749
|
+
setSemanticRenderTargets([]);
|
|
750
|
+
setSemanticBridgeDiagnostic(void 0);
|
|
751
|
+
setStateBridgeDiagnostic(void 0);
|
|
752
|
+
setRendererBridgeDiagnostic(void 0);
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
const session = createTileflowSessionController({ source: "react" });
|
|
756
|
+
const sessionStarter = createTileflowSessionStarter({
|
|
757
|
+
getSessionId: () => session.sessionId,
|
|
758
|
+
sessionId: session.sessionId,
|
|
759
|
+
source: "react"
|
|
760
|
+
});
|
|
761
|
+
const fairUseNotice = attachTileflowFairUseNotice(container);
|
|
762
|
+
registerCleanup(() => fairUseNotice.dispose());
|
|
763
|
+
registerTileflowContourProtocol({ addProtocol: maplibregl.addProtocol });
|
|
764
|
+
registerTileflowPmtilesProtocol({ addProtocol: maplibregl.addProtocol });
|
|
765
|
+
const worldRequestBridge = registerTileflowWorldRequestBridge({
|
|
766
|
+
addProtocol: maplibregl.addProtocol,
|
|
767
|
+
onNotice: fairUseNotice.update
|
|
768
|
+
});
|
|
769
|
+
registerCleanup(() => worldRequestBridge.dispose());
|
|
770
|
+
const transformRequest = createTileflowTransformRequest({
|
|
771
|
+
getAnalytics: () => resolvedAnalyticsRef.current,
|
|
772
|
+
sessionController: session,
|
|
773
|
+
sessionId: session.sessionId,
|
|
774
|
+
transformRequest: stableMapOptions?.transformRequest ?? void 0,
|
|
775
|
+
worldRequestBridge
|
|
776
|
+
});
|
|
777
|
+
const map = new maplibregl.Map({
|
|
778
|
+
...stableMapOptions,
|
|
779
|
+
container,
|
|
780
|
+
style: initialRuntimeStyle.style,
|
|
781
|
+
bearing: bearingRef.current,
|
|
782
|
+
center: centerRef.current,
|
|
783
|
+
pitch: pitchRef.current,
|
|
784
|
+
zoom: zoomRef.current,
|
|
785
|
+
interactive: resolvedInteractive,
|
|
786
|
+
attributionControl: stableMapOptions?.attributionControl ?? {
|
|
787
|
+
compact: true
|
|
788
|
+
},
|
|
789
|
+
transformRequest
|
|
790
|
+
});
|
|
791
|
+
mapRef.current = map;
|
|
792
|
+
setupResources.map = map;
|
|
793
|
+
registerCleanup(() => map.remove());
|
|
794
|
+
registerCleanup(attachTileflowAtmosphere(map));
|
|
795
|
+
const themeController = initialRuntimeStyle.theme === void 0 ? void 0 : createTileflowThemeController({
|
|
796
|
+
initial: initialRuntimeStyle,
|
|
797
|
+
map,
|
|
798
|
+
onTransition(transition) {
|
|
799
|
+
onThemeChangeRef.current?.(transition);
|
|
800
|
+
if (transition.phase === "preloading" || transition.phase === "applying") {
|
|
801
|
+
setThemeTransitionState("loading");
|
|
802
|
+
} else if (transition.phase === "error") {
|
|
803
|
+
setThemeTransitionState("error");
|
|
804
|
+
} else {
|
|
805
|
+
setThemeTransitionState("idle");
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
});
|
|
809
|
+
if (themeController) {
|
|
810
|
+
themeControllerRef.current = themeController;
|
|
811
|
+
registerCleanup(() => {
|
|
812
|
+
themeController.dispose();
|
|
813
|
+
if (themeControllerRef.current === themeController) {
|
|
814
|
+
themeControllerRef.current = void 0;
|
|
815
|
+
}
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
const vegetationController = createTileflowVegetation({
|
|
819
|
+
map,
|
|
820
|
+
...themeController ? { controller: themeController } : {},
|
|
821
|
+
visible: vegetationInputsRef.current.vegetation,
|
|
822
|
+
viewMode: vegetationInputsRef.current.viewMode,
|
|
823
|
+
// Direct tile readers use HTTP, not MapLibre's custom World protocol.
|
|
824
|
+
transformRequest: createTileflowTransformRequest({
|
|
825
|
+
getAnalytics: () => resolvedAnalyticsRef.current,
|
|
826
|
+
sessionController: session,
|
|
827
|
+
sessionId: session.sessionId,
|
|
828
|
+
transformRequest: (url) => stableMapOptions?.transformRequest?.(
|
|
829
|
+
url,
|
|
830
|
+
"Tile"
|
|
831
|
+
)
|
|
832
|
+
})
|
|
833
|
+
});
|
|
834
|
+
vegetationRef.current = vegetationController;
|
|
835
|
+
registerCleanup(() => {
|
|
836
|
+
vegetationController.dispose();
|
|
837
|
+
if (vegetationRef.current === vegetationController) vegetationRef.current = void 0;
|
|
838
|
+
});
|
|
839
|
+
const browserWindow = container.ownerDocument.defaultView;
|
|
840
|
+
if (!browserWindow) {
|
|
841
|
+
throw new Error("Tileflow semantic interactions require a browser document.");
|
|
842
|
+
}
|
|
843
|
+
const interactionCoordinator = createTileflowMapLibreInteractionCoordinator({
|
|
844
|
+
...controlledInteractionOwnershipRef.current ? { interactionState: coordinatedInteractionStateRef.current } : { defaultInteractionState: coordinatedInteractionStateRef.current },
|
|
845
|
+
onInteractionStateChange(state) {
|
|
846
|
+
if (!controlledInteractionOwnershipRef.current) {
|
|
847
|
+
coordinatedInteractionStateRef.current = state;
|
|
848
|
+
}
|
|
849
|
+
onInteractionStateChangeRef.current?.(state);
|
|
850
|
+
}
|
|
851
|
+
});
|
|
852
|
+
interactionCoordinatorRef.current = interactionCoordinator;
|
|
853
|
+
setupResources.coordinator = interactionCoordinator;
|
|
854
|
+
registerCleanup(() => interactionCoordinator.dispose());
|
|
855
|
+
const interactionRuntime = createTileflowMapLibreDomRuntime({
|
|
856
|
+
createMarker({ annotation, element }) {
|
|
857
|
+
const marker = new maplibregl.Marker({ element });
|
|
858
|
+
element.title = annotationTitlesRef.current.get(annotation.id) ?? annotation.ariaLabel;
|
|
859
|
+
return wrapMapLibrePositioned(marker);
|
|
860
|
+
},
|
|
861
|
+
createOverlay({ container: overlayContainer, kind }) {
|
|
862
|
+
const popup = new maplibregl.Popup({
|
|
863
|
+
closeButton: false,
|
|
864
|
+
closeOnClick: false,
|
|
865
|
+
closeOnMove: false,
|
|
866
|
+
focusAfterOpen: false,
|
|
867
|
+
offset: kind === "popup" ? 24 : 18
|
|
868
|
+
}).setDOMContent(overlayContainer);
|
|
869
|
+
return wrapMapLibrePositioned(popup);
|
|
870
|
+
},
|
|
871
|
+
customMarker: renderMarkerRef.current !== void 0,
|
|
872
|
+
customPopup: renderPopupRef.current !== void 0,
|
|
873
|
+
customTooltip: renderTooltipRef.current !== void 0,
|
|
874
|
+
document: container.ownerDocument,
|
|
875
|
+
interactionState: interactionCoordinator.getInteractionState(),
|
|
876
|
+
map,
|
|
877
|
+
onInteractionStateChange: interactionCoordinator.requestInteractionState,
|
|
878
|
+
onDiagnostic(diagnostic) {
|
|
879
|
+
onInteractionDiagnosticRef.current?.(diagnostic);
|
|
880
|
+
},
|
|
881
|
+
updateMarker(_marker, { annotation, element }) {
|
|
882
|
+
element.title = annotationTitlesRef.current.get(annotation.id) ?? annotation.ariaLabel;
|
|
883
|
+
}
|
|
884
|
+
});
|
|
885
|
+
interactionRuntimeRef.current = interactionRuntime;
|
|
886
|
+
setupResources.annotationRuntime = interactionRuntime;
|
|
887
|
+
registerCleanup(() => interactionRuntime.dispose());
|
|
888
|
+
const semanticRuntime = createTileflowMapLibreSemanticDomRuntime({
|
|
889
|
+
cancelFrame: (frame) => browserWindow.cancelAnimationFrame(frame),
|
|
890
|
+
createOverlay({ container: overlayContainer, kind }) {
|
|
891
|
+
const popup = new maplibregl.Popup({
|
|
892
|
+
closeButton: false,
|
|
893
|
+
closeOnClick: false,
|
|
894
|
+
closeOnMove: false,
|
|
895
|
+
focusAfterOpen: false,
|
|
896
|
+
offset: kind === "popup" ? 24 : 18
|
|
897
|
+
}).setDOMContent(overlayContainer);
|
|
898
|
+
return wrapMapLibrePositioned(popup);
|
|
899
|
+
},
|
|
900
|
+
customPopup: renderPopupRef.current !== void 0,
|
|
901
|
+
customTooltip: renderTooltipRef.current !== void 0,
|
|
902
|
+
document: container.ownerDocument,
|
|
903
|
+
interactionState: interactionCoordinator.getInteractionState(),
|
|
904
|
+
map,
|
|
905
|
+
onDiagnostic(diagnostic) {
|
|
906
|
+
onInteractionDiagnosticRef.current?.(diagnostic);
|
|
907
|
+
},
|
|
908
|
+
onInteractionStateChange: interactionCoordinator.requestInteractionState,
|
|
909
|
+
poiMap: createTileflowMapLibrePoiMap(map),
|
|
910
|
+
requestFrame: (callback) => browserWindow.requestAnimationFrame(() => callback())
|
|
911
|
+
});
|
|
912
|
+
semanticRuntimeRef.current = semanticRuntime;
|
|
913
|
+
setupResources.semanticRuntime = semanticRuntime;
|
|
914
|
+
registerCleanup(() => semanticRuntime.dispose());
|
|
915
|
+
const detachAnnotationRuntime = interactionCoordinator.attach(
|
|
916
|
+
"annotation",
|
|
917
|
+
interactionRuntime
|
|
918
|
+
);
|
|
919
|
+
registerCleanup(detachAnnotationRuntime);
|
|
920
|
+
const detachSemanticRuntime = interactionCoordinator.attach("semantic", semanticRuntime);
|
|
921
|
+
registerCleanup(detachSemanticRuntime);
|
|
922
|
+
const unsubscribeInteractionEvents = interactionRuntime.subscribeEvents((event) => {
|
|
923
|
+
onInteractionEventRef.current?.(event);
|
|
924
|
+
});
|
|
925
|
+
registerCleanup(unsubscribeInteractionEvents);
|
|
926
|
+
const unsubscribeDiagnostics = interactionRuntime.subscribeDiagnostics((diagnostics) => {
|
|
927
|
+
setAnnotationDiagnostics(diagnostics);
|
|
928
|
+
});
|
|
929
|
+
registerCleanup(unsubscribeDiagnostics);
|
|
930
|
+
const unsubscribeRenderTargets = interactionRuntime.subscribeRenderTargets((targets) => {
|
|
931
|
+
setAnnotationRenderTargets(targets);
|
|
932
|
+
});
|
|
933
|
+
registerCleanup(unsubscribeRenderTargets);
|
|
934
|
+
const unsubscribeSemanticEvents = semanticRuntime.subscribeEvents((event) => {
|
|
935
|
+
if (event.target.kind === "annotation") return;
|
|
936
|
+
onInteractionEventRef.current?.({ ...event, target: event.target });
|
|
937
|
+
});
|
|
938
|
+
registerCleanup(unsubscribeSemanticEvents);
|
|
939
|
+
const unsubscribeSemanticDiagnostics = semanticRuntime.subscribeDiagnostics(
|
|
940
|
+
(diagnostics) => {
|
|
941
|
+
setSemanticDiagnostics(diagnostics);
|
|
942
|
+
}
|
|
943
|
+
);
|
|
944
|
+
registerCleanup(unsubscribeSemanticDiagnostics);
|
|
945
|
+
const unsubscribeSemanticRenderTargets = semanticRuntime.subscribeRenderTargets(
|
|
946
|
+
(targets) => {
|
|
947
|
+
setSemanticRenderTargets(targets);
|
|
948
|
+
}
|
|
949
|
+
);
|
|
950
|
+
registerCleanup(unsubscribeSemanticRenderTargets);
|
|
951
|
+
setAnnotationDiagnostics(interactionRuntime.getDiagnostics());
|
|
952
|
+
setAnnotationRenderTargets(interactionRuntime.getRenderTargets());
|
|
953
|
+
setSemanticDiagnostics(semanticRuntime.getDiagnostics());
|
|
954
|
+
setSemanticRenderTargets(semanticRuntime.getRenderTargets());
|
|
955
|
+
try {
|
|
956
|
+
setAnnotationBridgeDiagnostic(void 0);
|
|
957
|
+
interactionRuntime.reconcile(annotationsRef.current);
|
|
958
|
+
} catch (error) {
|
|
959
|
+
console.error("Failed to reconcile Tileflow annotations", error);
|
|
960
|
+
const diagnostic = createOverlayFailureDiagnostic(
|
|
961
|
+
"Tileflow annotations could not be reconciled."
|
|
962
|
+
);
|
|
963
|
+
setAnnotationBridgeDiagnostic(diagnostic);
|
|
964
|
+
onInteractionDiagnosticRef.current?.(diagnostic);
|
|
965
|
+
}
|
|
966
|
+
const initialInteractions = preparedInteractionsRef.current;
|
|
967
|
+
if (initialInteractions.ok) {
|
|
968
|
+
try {
|
|
969
|
+
setSemanticBridgeDiagnostic(void 0);
|
|
970
|
+
semanticRuntime.reconcile(initialInteractions.bindings);
|
|
971
|
+
} catch (error) {
|
|
972
|
+
console.error("Failed to reconcile Tileflow semantic interactions", error);
|
|
973
|
+
const diagnostic = createOverlayFailureDiagnostic(
|
|
974
|
+
"Tileflow semantic interactions could not be reconciled."
|
|
975
|
+
);
|
|
976
|
+
setSemanticBridgeDiagnostic(diagnostic);
|
|
977
|
+
onInteractionDiagnosticRef.current?.(diagnostic);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
const controls = resolvedInteractive ? new maplibregl.NavigationControl() : null;
|
|
981
|
+
if (controls) {
|
|
982
|
+
map.addControl(controls, "top-right");
|
|
983
|
+
}
|
|
984
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
985
|
+
map.resize();
|
|
986
|
+
});
|
|
987
|
+
registerCleanup(() => resizeObserver.disconnect());
|
|
988
|
+
resizeObserver.observe(container);
|
|
989
|
+
const lifecycle = attachTileflowMapLifecycle({
|
|
990
|
+
getSession: () => {
|
|
991
|
+
const analyticsForLoad = resolvedAnalyticsRef.current;
|
|
992
|
+
return {
|
|
993
|
+
analytics: analyticsForLoad,
|
|
994
|
+
styleId: analyticsForLoad?.styleId ?? (typeof initialRuntimeStyle.style === "string" ? initialRuntimeStyle.style : mapNameRef.current)
|
|
995
|
+
};
|
|
996
|
+
},
|
|
997
|
+
map,
|
|
998
|
+
onLoad: (loadedMap) => {
|
|
999
|
+
onLoadRef.current?.(loadedMap);
|
|
1000
|
+
},
|
|
1001
|
+
scheduler: {
|
|
1002
|
+
cancelFrame: (frame) => cancelAnimationFrame(frame),
|
|
1003
|
+
requestFrame: (callback) => requestAnimationFrame(callback)
|
|
1004
|
+
},
|
|
1005
|
+
sessionStarter,
|
|
1006
|
+
setState: setCaptureState,
|
|
1007
|
+
subscribe: (subscribedMap, event, listener) => {
|
|
1008
|
+
const subscription = subscribedMap.on(event, listener);
|
|
1009
|
+
return () => subscription.unsubscribe();
|
|
1010
|
+
}
|
|
1011
|
+
});
|
|
1012
|
+
registerCleanup(() => lifecycle.dispose());
|
|
1013
|
+
const latestRuntimeStyle = runtimeStyleRef.current;
|
|
1014
|
+
const latestBlend = blendRequestRef.current;
|
|
1015
|
+
if (themeController && latestBlend) {
|
|
1016
|
+
if (latestRuntimeStyle) setActiveRuntimeResource(latestRuntimeStyle);
|
|
1017
|
+
void themeController.setBlend(latestBlend, { transition: themeTransitionRef.current });
|
|
1018
|
+
} else if (themeController && latestRuntimeStyle && latestRuntimeStyle !== initialRuntimeStyle) {
|
|
1019
|
+
setActiveRuntimeResource(latestRuntimeStyle);
|
|
1020
|
+
void themeController.setTheme(latestRuntimeStyle, {
|
|
1021
|
+
transition: themeTransitionRef.current
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
}).catch((error) => {
|
|
1025
|
+
try {
|
|
1026
|
+
disposeMap?.();
|
|
1027
|
+
} catch (cleanupError) {
|
|
1028
|
+
console.error("Failed to clean up the Tileflow map runtime", cleanupError);
|
|
1029
|
+
}
|
|
1030
|
+
if (!cancelled) {
|
|
1031
|
+
console.error("Failed to load the Tileflow map runtime", error);
|
|
1032
|
+
setCaptureState("error");
|
|
1033
|
+
}
|
|
212
1034
|
});
|
|
213
1035
|
return () => {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
mapRef.current = null;
|
|
1036
|
+
cancelled = true;
|
|
1037
|
+
readinessRunRef.current += 1;
|
|
1038
|
+
disposeMap?.();
|
|
218
1039
|
};
|
|
219
|
-
}, [
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
1040
|
+
}, [resolvedInteractive, runtimeMapIdentity, runtimeStyleReady, stableMapOptions]);
|
|
1041
|
+
useEffect(() => {
|
|
1042
|
+
const controller = themeControllerRef.current;
|
|
1043
|
+
if (!controller || themeBlendKey) return;
|
|
1044
|
+
if (themeResolution.error) {
|
|
1045
|
+
void controller.setTheme(controller.getCurrent());
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
if (!runtimeStyle || controller.getCurrent() === runtimeStyle && !controller.getBlend())
|
|
1049
|
+
return;
|
|
1050
|
+
setActiveRuntimeResource(runtimeStyle);
|
|
1051
|
+
void controller.setTheme(runtimeStyle, { transition: themeTransitionRef.current }).then((result) => {
|
|
1052
|
+
if (result.status === "failed") {
|
|
1053
|
+
console.error("Failed to change the Tileflow map theme", result.error);
|
|
1054
|
+
}
|
|
1055
|
+
});
|
|
1056
|
+
}, [runtimeStyle, themeBlendKey, themeResolution.error]);
|
|
1057
|
+
useEffect(() => {
|
|
1058
|
+
const controller = themeControllerRef.current;
|
|
1059
|
+
if (!controller || !blendStyles || themeBlendPosition === void 0) return;
|
|
1060
|
+
if (runtimeStyle) setActiveRuntimeResource(runtimeStyle);
|
|
1061
|
+
void controller.setBlend(
|
|
1062
|
+
{ hold: themeBlendHold, position: themeBlendPosition, themes: blendStyles },
|
|
1063
|
+
{ transition: themeTransitionRef.current }
|
|
1064
|
+
).then((result) => {
|
|
1065
|
+
if (result.status === "failed") {
|
|
1066
|
+
console.error("Failed to blend the Tileflow map themes", result.error);
|
|
1067
|
+
}
|
|
1068
|
+
});
|
|
1069
|
+
}, [blendStyles, runtimeStyle, themeBlendHold, themeBlendPosition]);
|
|
1070
|
+
useLayoutEffect(() => {
|
|
1071
|
+
if (!isImageMode) return;
|
|
1072
|
+
readinessRunRef.current += 1;
|
|
1073
|
+
setActiveRuntimeResource(runtimeImageUrl);
|
|
1074
|
+
setCaptureState("loading");
|
|
1075
|
+
const image = imageRef.current;
|
|
1076
|
+
if (image?.complete) {
|
|
1077
|
+
if (image.naturalWidth === 0) setCaptureState("error");
|
|
1078
|
+
else void markImageReady(image);
|
|
1079
|
+
}
|
|
1080
|
+
}, [isImageMode, runtimeImageUrl]);
|
|
1081
|
+
async function markImageReady(image) {
|
|
1082
|
+
const run = ++readinessRunRef.current;
|
|
1083
|
+
try {
|
|
1084
|
+
if (typeof image.decode === "function") await image.decode();
|
|
1085
|
+
} catch {
|
|
1086
|
+
if (!image.complete || image.naturalWidth === 0) {
|
|
1087
|
+
if (readinessRunRef.current === run) setCaptureState("error");
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
if (readinessRunRef.current === run) setCaptureState("idle");
|
|
1092
|
+
}
|
|
231
1093
|
const frameStyle = {
|
|
232
1094
|
height,
|
|
233
1095
|
minHeight: 240,
|
|
@@ -235,228 +1097,320 @@ function Map({
|
|
|
235
1097
|
position: "relative",
|
|
236
1098
|
width: "100%"
|
|
237
1099
|
};
|
|
1100
|
+
const diagnosticView = interactionDiagnostic ? /* @__PURE__ */ jsx(
|
|
1101
|
+
"div",
|
|
1102
|
+
{
|
|
1103
|
+
"data-tileflow-interaction-diagnostic": interactionDiagnostic.code,
|
|
1104
|
+
role: "alert",
|
|
1105
|
+
style: interactionDiagnosticStyle,
|
|
1106
|
+
children: interactionDiagnostic.message
|
|
1107
|
+
}
|
|
1108
|
+
) : null;
|
|
238
1109
|
if (isImageMode) {
|
|
239
|
-
return /* @__PURE__ */
|
|
240
|
-
"
|
|
1110
|
+
return /* @__PURE__ */ jsxs(
|
|
1111
|
+
"div",
|
|
241
1112
|
{
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
1113
|
+
className,
|
|
1114
|
+
"data-tileflow-capture-id": resolvedCaptureId,
|
|
1115
|
+
"data-tileflow-map": mapName,
|
|
1116
|
+
"data-tileflow-theme": resolvedThemeName,
|
|
1117
|
+
"data-tileflow-state": effectiveCaptureState,
|
|
1118
|
+
ref: containerRef,
|
|
1119
|
+
style: frameStyle,
|
|
1120
|
+
children: [
|
|
1121
|
+
runtimeImageUrl ? /* @__PURE__ */ jsx(
|
|
1122
|
+
"img",
|
|
1123
|
+
{
|
|
1124
|
+
alt,
|
|
1125
|
+
decoding: "async",
|
|
1126
|
+
loading: imageLoading,
|
|
1127
|
+
onError: () => {
|
|
1128
|
+
readinessRunRef.current += 1;
|
|
1129
|
+
setCaptureState("error");
|
|
1130
|
+
},
|
|
1131
|
+
onLoad: (event) => void markImageReady(event.currentTarget),
|
|
1132
|
+
ref: imageRef,
|
|
1133
|
+
src: runtimeImageUrl,
|
|
1134
|
+
style: {
|
|
1135
|
+
display: "block",
|
|
1136
|
+
height: "100%",
|
|
1137
|
+
objectFit: "cover",
|
|
1138
|
+
width: "100%"
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
) : null,
|
|
1142
|
+
diagnosticView
|
|
1143
|
+
]
|
|
252
1144
|
}
|
|
253
|
-
)
|
|
1145
|
+
);
|
|
254
1146
|
}
|
|
255
|
-
return /* @__PURE__ */
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
1147
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1148
|
+
/* @__PURE__ */ jsxs(
|
|
1149
|
+
"div",
|
|
1150
|
+
{
|
|
1151
|
+
className,
|
|
1152
|
+
"data-tileflow-capture-id": resolvedCaptureId,
|
|
1153
|
+
"data-tileflow-map": mapName,
|
|
1154
|
+
"data-tileflow-theme": resolvedThemeName,
|
|
1155
|
+
"data-tileflow-state": effectiveCaptureState,
|
|
1156
|
+
style: frameStyle,
|
|
1157
|
+
children: [
|
|
1158
|
+
/* @__PURE__ */ jsx("div", { ref: containerRef, style: mapContainerStyle }),
|
|
1159
|
+
diagnosticView
|
|
1160
|
+
]
|
|
1161
|
+
}
|
|
1162
|
+
),
|
|
1163
|
+
annotationRenderTargets.map((target) => {
|
|
1164
|
+
const portalKey = `annotation:${target.key}`;
|
|
1165
|
+
return createPortal(
|
|
1166
|
+
/* @__PURE__ */ jsx(
|
|
1167
|
+
TileflowAnnotationPortal,
|
|
1168
|
+
{
|
|
1169
|
+
onCommit: portalReadiness.markCommitted,
|
|
1170
|
+
portalKey,
|
|
1171
|
+
readinessToken: portalReadiness.token,
|
|
1172
|
+
renderMarker,
|
|
1173
|
+
renderPopup: generalRenderPopup,
|
|
1174
|
+
renderTarget: target,
|
|
1175
|
+
renderTooltip: generalRenderTooltip
|
|
1176
|
+
}
|
|
1177
|
+
),
|
|
1178
|
+
target.container,
|
|
1179
|
+
portalKey
|
|
1180
|
+
);
|
|
1181
|
+
}),
|
|
1182
|
+
semanticRenderTargets.map((target) => {
|
|
1183
|
+
const portalKey = `semantic:${target.key}`;
|
|
1184
|
+
return createPortal(
|
|
1185
|
+
/* @__PURE__ */ jsx(
|
|
1186
|
+
TileflowSemanticPortal,
|
|
1187
|
+
{
|
|
1188
|
+
onCommit: portalReadiness.markCommitted,
|
|
1189
|
+
portalKey,
|
|
1190
|
+
readinessToken: portalReadiness.token,
|
|
1191
|
+
renderPopup: generalRenderPopup,
|
|
1192
|
+
renderTarget: target,
|
|
1193
|
+
renderTooltip: generalRenderTooltip
|
|
1194
|
+
}
|
|
1195
|
+
),
|
|
1196
|
+
target.container,
|
|
1197
|
+
portalKey
|
|
1198
|
+
);
|
|
1199
|
+
})
|
|
1200
|
+
] });
|
|
260
1201
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
1202
|
+
var mapContainerStyle = {
|
|
1203
|
+
inset: 0,
|
|
1204
|
+
position: "absolute"
|
|
1205
|
+
};
|
|
1206
|
+
var interactionDiagnosticStyle = {
|
|
1207
|
+
background: "#7f1d1d",
|
|
1208
|
+
border: "1px solid #fecaca",
|
|
1209
|
+
borderRadius: 4,
|
|
1210
|
+
color: "#ffffff",
|
|
1211
|
+
fontFamily: "system-ui, sans-serif",
|
|
1212
|
+
fontSize: 13,
|
|
1213
|
+
left: 12,
|
|
1214
|
+
maxWidth: "calc(100% - 24px)",
|
|
1215
|
+
padding: "8px 10px",
|
|
1216
|
+
position: "absolute",
|
|
1217
|
+
top: 12,
|
|
1218
|
+
zIndex: 10
|
|
1219
|
+
};
|
|
1220
|
+
function TileflowAnnotationPortal({
|
|
1221
|
+
onCommit,
|
|
1222
|
+
portalKey,
|
|
1223
|
+
readinessToken,
|
|
1224
|
+
renderMarker,
|
|
1225
|
+
renderPopup,
|
|
1226
|
+
renderTarget,
|
|
1227
|
+
renderTooltip
|
|
1228
|
+
}) {
|
|
1229
|
+
useLayoutEffect(() => {
|
|
1230
|
+
onCommit(readinessToken, portalKey);
|
|
1231
|
+
}, [onCommit, portalKey, readinessToken]);
|
|
1232
|
+
const { annotation } = renderTarget;
|
|
1233
|
+
const content = renderTarget.kind === "marker" ? annotation.marker?.content : renderTarget.kind === "tooltip" ? annotation.tooltip?.content : annotation.popup?.content;
|
|
1234
|
+
const context = {
|
|
1235
|
+
annotation,
|
|
1236
|
+
close: renderTarget.close,
|
|
1237
|
+
...content ? { content } : {},
|
|
1238
|
+
target: {
|
|
1239
|
+
annotation,
|
|
1240
|
+
coordinate: annotation.coordinate,
|
|
1241
|
+
kind: "annotation"
|
|
1242
|
+
},
|
|
1243
|
+
...content?.kind === "view" ? { viewName: content.name } : {}
|
|
1244
|
+
};
|
|
1245
|
+
if (renderTarget.kind === "marker") return renderMarker?.(context) ?? null;
|
|
1246
|
+
const renderer = renderTarget.kind === "tooltip" ? renderTooltip : renderPopup;
|
|
1247
|
+
return renderer?.(context) ?? null;
|
|
264
1248
|
}
|
|
265
|
-
function
|
|
266
|
-
|
|
1249
|
+
function TileflowSemanticPortal({
|
|
1250
|
+
onCommit,
|
|
1251
|
+
portalKey,
|
|
1252
|
+
readinessToken,
|
|
1253
|
+
renderPopup,
|
|
1254
|
+
renderTarget,
|
|
1255
|
+
renderTooltip
|
|
1256
|
+
}) {
|
|
1257
|
+
useLayoutEffect(() => {
|
|
1258
|
+
onCommit(readinessToken, portalKey);
|
|
1259
|
+
}, [onCommit, portalKey, readinessToken]);
|
|
1260
|
+
const context = {
|
|
1261
|
+
close: renderTarget.close,
|
|
1262
|
+
content: renderTarget.content,
|
|
1263
|
+
target: renderTarget.target,
|
|
1264
|
+
...renderTarget.viewName ? { viewName: renderTarget.viewName } : {}
|
|
1265
|
+
};
|
|
1266
|
+
const renderer = renderTarget.kind === "tooltip" ? renderTooltip : renderPopup;
|
|
1267
|
+
return renderer?.(context) ?? null;
|
|
267
1268
|
}
|
|
268
|
-
function
|
|
269
|
-
|
|
270
|
-
|
|
1269
|
+
function useTileflowPortalReadiness(portalKeys) {
|
|
1270
|
+
const signature = JSON.stringify([...portalKeys].sort());
|
|
1271
|
+
const versionRef = useRef({ signature, version: 0 });
|
|
1272
|
+
if (versionRef.current.signature !== signature) {
|
|
1273
|
+
versionRef.current = { signature, version: versionRef.current.version + 1 };
|
|
271
1274
|
}
|
|
272
|
-
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
return void 0;
|
|
1275
|
+
const token = `${versionRef.current.version}:${signature}`;
|
|
1276
|
+
const expectedKeysRef = useRef({ keys: [...portalKeys].sort(), token });
|
|
1277
|
+
if (expectedKeysRef.current.token !== token) {
|
|
1278
|
+
expectedKeysRef.current = { keys: [...portalKeys].sort(), token };
|
|
277
1279
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
1280
|
+
const expectedKeys = expectedKeysRef.current.keys;
|
|
1281
|
+
const [committed, setCommitted] = useState(() => ({
|
|
1282
|
+
keys: /* @__PURE__ */ new Set(),
|
|
1283
|
+
token
|
|
1284
|
+
}));
|
|
1285
|
+
const [readyToken, setReadyToken] = useState(token);
|
|
1286
|
+
const readinessRunRef = useRef(0);
|
|
1287
|
+
const markCommitted = useCallback((committedToken, portalKey) => {
|
|
1288
|
+
setCommitted((current) => {
|
|
1289
|
+
if (current.token !== committedToken) {
|
|
1290
|
+
return { keys: /* @__PURE__ */ new Set([portalKey]), token: committedToken };
|
|
1291
|
+
}
|
|
1292
|
+
if (current.keys.has(portalKey)) return current;
|
|
1293
|
+
return { keys: /* @__PURE__ */ new Set([...current.keys, portalKey]), token: committedToken };
|
|
1294
|
+
});
|
|
1295
|
+
}, []);
|
|
1296
|
+
useLayoutEffect(() => {
|
|
1297
|
+
setCommitted((current) => current.token === token ? current : { keys: /* @__PURE__ */ new Set(), token });
|
|
1298
|
+
}, [token]);
|
|
1299
|
+
useLayoutEffect(() => {
|
|
1300
|
+
if (readyToken === token || committed.token !== token || !expectedKeys.every((key) => committed.keys.has(key))) {
|
|
1301
|
+
return;
|
|
287
1302
|
}
|
|
288
|
-
|
|
289
|
-
|
|
1303
|
+
const browserWindow = globalThis.window;
|
|
1304
|
+
const run = ++readinessRunRef.current;
|
|
1305
|
+
let secondFrame;
|
|
1306
|
+
const firstFrame = browserWindow.requestAnimationFrame(() => {
|
|
1307
|
+
secondFrame = browserWindow.requestAnimationFrame(() => {
|
|
1308
|
+
if (readinessRunRef.current === run) setReadyToken(token);
|
|
1309
|
+
});
|
|
1310
|
+
});
|
|
1311
|
+
return () => {
|
|
1312
|
+
readinessRunRef.current += 1;
|
|
1313
|
+
browserWindow.cancelAnimationFrame(firstFrame);
|
|
1314
|
+
if (secondFrame !== void 0) browserWindow.cancelAnimationFrame(secondFrame);
|
|
1315
|
+
};
|
|
1316
|
+
}, [committed, expectedKeys, readyToken, token]);
|
|
1317
|
+
return { idle: readyToken === token, markCommitted, token };
|
|
1318
|
+
}
|
|
1319
|
+
function wrapMapLibrePositioned(positioned) {
|
|
1320
|
+
return {
|
|
1321
|
+
addTo(map) {
|
|
1322
|
+
positioned.addTo(map);
|
|
1323
|
+
},
|
|
1324
|
+
remove() {
|
|
1325
|
+
positioned.remove();
|
|
1326
|
+
},
|
|
1327
|
+
setLngLat(coordinate) {
|
|
1328
|
+
positioned.setLngLat([coordinate[0], coordinate[1]]);
|
|
290
1329
|
}
|
|
291
|
-
return { url: nextUrl.toString() };
|
|
292
1330
|
};
|
|
293
1331
|
}
|
|
294
|
-
function
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
mapId: input.mapId,
|
|
301
|
-
metadata: analytics.metadata,
|
|
302
|
-
referrer: typeof document === "undefined" ? void 0 : document.referrer || "",
|
|
303
|
-
sdkVersion: analytics.sdkVersion,
|
|
304
|
-
sessionId: input.sessionId,
|
|
305
|
-
source: analytics.source ?? "react",
|
|
306
|
-
styleId: input.styleId,
|
|
307
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
308
|
-
});
|
|
309
|
-
const blob = new Blob([payload], { type: "application/json" });
|
|
310
|
-
if (navigator.sendBeacon?.(endpoint, blob)) {
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
void fetch(endpoint, {
|
|
314
|
-
body: payload,
|
|
315
|
-
credentials: "omit",
|
|
316
|
-
headers: {
|
|
317
|
-
"Content-Type": "application/json"
|
|
1332
|
+
function createTileflowMapLibrePoiMap(map) {
|
|
1333
|
+
const poiMap = map;
|
|
1334
|
+
return {
|
|
1335
|
+
getStyle: () => map.getStyle(),
|
|
1336
|
+
on(event, listener) {
|
|
1337
|
+
return poiMap.on(event, listener);
|
|
318
1338
|
},
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
return normalizeUrl(analytics.apiUrl);
|
|
326
|
-
}
|
|
327
|
-
if (analytics.endpoint) {
|
|
328
|
-
const url = new URL(analytics.endpoint);
|
|
329
|
-
return url.origin;
|
|
330
|
-
}
|
|
331
|
-
return "https://api.tileflow.dev";
|
|
1339
|
+
queryRenderedFeatures(point, options) {
|
|
1340
|
+
return map.queryRenderedFeatures(point, {
|
|
1341
|
+
layers: [...options.layers]
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1344
|
+
};
|
|
332
1345
|
}
|
|
333
|
-
function
|
|
334
|
-
if (userAnalytics?.enabled === false) {
|
|
335
|
-
return userAnalytics;
|
|
336
|
-
}
|
|
337
|
-
if (!userAnalytics && !runtimeAnalytics?.mapId) {
|
|
338
|
-
return void 0;
|
|
339
|
-
}
|
|
1346
|
+
function createOverlayFailureDiagnostic(message) {
|
|
340
1347
|
return {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
styleId: userAnalytics?.styleId ?? runtimeAnalytics?.styleId
|
|
1348
|
+
code: "OVERLAY_FAILURE",
|
|
1349
|
+
level: "error",
|
|
1350
|
+
message,
|
|
1351
|
+
path: ""
|
|
346
1352
|
};
|
|
347
1353
|
}
|
|
348
|
-
function
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
}
|
|
357
|
-
function isTilePath(url) {
|
|
358
|
-
return url.pathname.startsWith("/tiles/") || url.pathname.startsWith("/v1/tiles/");
|
|
1354
|
+
function tileflowInteractionDiagnosticKey(diagnostic) {
|
|
1355
|
+
return [
|
|
1356
|
+
diagnostic.code,
|
|
1357
|
+
diagnostic.level,
|
|
1358
|
+
diagnostic.path ?? "",
|
|
1359
|
+
diagnostic.target ? JSON.stringify(diagnostic.target) : "",
|
|
1360
|
+
diagnostic.message
|
|
1361
|
+
].join("\0");
|
|
359
1362
|
}
|
|
360
|
-
function
|
|
361
|
-
|
|
362
|
-
|
|
1363
|
+
function runTileflowReactCleanups(cleanups) {
|
|
1364
|
+
const errors = [];
|
|
1365
|
+
for (const cleanup of cleanups) {
|
|
1366
|
+
try {
|
|
1367
|
+
cleanup();
|
|
1368
|
+
} catch (error) {
|
|
1369
|
+
errors.push(error);
|
|
1370
|
+
}
|
|
363
1371
|
}
|
|
364
|
-
|
|
1372
|
+
if (errors.length === 1) throw errors[0];
|
|
1373
|
+
if (errors.length > 1) throw new AggregateError(errors, "Tileflow React cleanup failed.");
|
|
365
1374
|
}
|
|
366
|
-
function
|
|
367
|
-
|
|
368
|
-
manifestCache.set(
|
|
369
|
-
url,
|
|
370
|
-
fetch(url, { cache: "no-store" }).then(async (response) => {
|
|
371
|
-
if (response.status === 404) {
|
|
372
|
-
return null;
|
|
373
|
-
}
|
|
374
|
-
if (!response.ok) {
|
|
375
|
-
throw new Error(`Tileflow manifest failed: ${response.status}`);
|
|
376
|
-
}
|
|
377
|
-
return await response.json();
|
|
378
|
-
})
|
|
379
|
-
);
|
|
380
|
-
}
|
|
381
|
-
return manifestCache.get(url);
|
|
1375
|
+
function useStableMapOptions(options) {
|
|
1376
|
+
return useStableMapOptionValue(options);
|
|
382
1377
|
}
|
|
383
|
-
function
|
|
384
|
-
const
|
|
385
|
-
if (!
|
|
386
|
-
|
|
387
|
-
}
|
|
388
|
-
if (typeof entry === "string") {
|
|
389
|
-
const analytics2 = inferAnalyticsFromStyleUrl(entry);
|
|
390
|
-
return {
|
|
391
|
-
apiUrl: analytics2?.apiUrl ?? manifest.apiUrl,
|
|
392
|
-
mapId: analytics2?.mapId,
|
|
393
|
-
styleId: analytics2?.styleId,
|
|
394
|
-
styleUrl: entry
|
|
395
|
-
};
|
|
1378
|
+
function useStableMapOptionValue(value) {
|
|
1379
|
+
const valueRef = useRef(value);
|
|
1380
|
+
if (!areEquivalentMapOptionValues(valueRef.current, value)) {
|
|
1381
|
+
valueRef.current = value;
|
|
396
1382
|
}
|
|
397
|
-
|
|
398
|
-
if (!styleUrl && !entry.mapId) {
|
|
399
|
-
return null;
|
|
400
|
-
}
|
|
401
|
-
const analytics = styleUrl ? inferAnalyticsFromStyleUrl(styleUrl) : void 0;
|
|
402
|
-
return {
|
|
403
|
-
apiUrl: entry.apiUrl ?? manifest.apiUrl ?? analytics?.apiUrl,
|
|
404
|
-
mapId: entry.mapId ?? analytics?.mapId,
|
|
405
|
-
styleId: entry.styleId ?? analytics?.styleId,
|
|
406
|
-
styleUrl
|
|
407
|
-
};
|
|
1383
|
+
return valueRef.current;
|
|
408
1384
|
}
|
|
409
|
-
function
|
|
410
|
-
if (
|
|
411
|
-
return
|
|
1385
|
+
function areEquivalentMapOptionValues(left, right, seen = /* @__PURE__ */ new WeakMap()) {
|
|
1386
|
+
if (Object.is(left, right)) {
|
|
1387
|
+
return true;
|
|
412
1388
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
url.searchParams.set("center", input.center.join(","));
|
|
416
|
-
url.searchParams.set("zoom", String(input.zoom));
|
|
417
|
-
url.searchParams.set("width", String(input.imageSize.width));
|
|
418
|
-
url.searchParams.set("height", String(input.imageSize.height));
|
|
419
|
-
return url.toString();
|
|
420
|
-
}
|
|
421
|
-
function normalizeStaticImageSize(input) {
|
|
422
|
-
let width = clampDimension(input.width);
|
|
423
|
-
let height = clampDimension(input.height);
|
|
424
|
-
const pixelCount = width * height;
|
|
425
|
-
if (pixelCount > maxStaticImagePixels) {
|
|
426
|
-
const scale = Math.sqrt(maxStaticImagePixels / pixelCount);
|
|
427
|
-
width = clampDimension(width * scale);
|
|
428
|
-
height = clampDimension(height * scale);
|
|
1389
|
+
if (!left || !right || typeof left !== "object" || typeof right !== "object") {
|
|
1390
|
+
return false;
|
|
429
1391
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
if (!Number.isFinite(value) || value <= 0) {
|
|
434
|
-
return 640;
|
|
1392
|
+
const seenRight = seen.get(left);
|
|
1393
|
+
if (seenRight) {
|
|
1394
|
+
return seenRight === right;
|
|
435
1395
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
try {
|
|
440
|
-
const url = new URL(styleUrl, getLocationOrigin());
|
|
441
|
-
const match = url.pathname.match(/\/(?:v1\/)?maps\/([^/]+)\/style\.json$/);
|
|
442
|
-
if (!match?.[1]) {
|
|
443
|
-
return void 0;
|
|
444
|
-
}
|
|
445
|
-
return {
|
|
446
|
-
apiUrl: url.origin,
|
|
447
|
-
mapId: match[1],
|
|
448
|
-
styleId: url.toString()
|
|
449
|
-
};
|
|
450
|
-
} catch {
|
|
451
|
-
return void 0;
|
|
1396
|
+
seen.set(left, right);
|
|
1397
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
1398
|
+
return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index) => areEquivalentMapOptionValues(value, right[index], seen));
|
|
452
1399
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
if (typeof window === "undefined") {
|
|
456
|
-
return "http://localhost";
|
|
1400
|
+
if (!isPlainObject(left) || !isPlainObject(right)) {
|
|
1401
|
+
return false;
|
|
457
1402
|
}
|
|
458
|
-
|
|
1403
|
+
const leftKeys = Object.keys(left);
|
|
1404
|
+
const rightKeys = Object.keys(right);
|
|
1405
|
+
return leftKeys.length === rightKeys.length && leftKeys.every(
|
|
1406
|
+
(key) => Object.hasOwn(right, key) && areEquivalentMapOptionValues(left[key], right[key], seen)
|
|
1407
|
+
);
|
|
1408
|
+
}
|
|
1409
|
+
function isPlainObject(value) {
|
|
1410
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1411
|
+
return prototype === Object.prototype || prototype === null;
|
|
459
1412
|
}
|
|
460
1413
|
export {
|
|
461
|
-
Map
|
|
1414
|
+
Map2 as Map,
|
|
1415
|
+
configureTileflowMapLibre
|
|
462
1416
|
};
|