@tileflow/dev 0.1.0-alpha.31 → 0.1.0-alpha.33
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/README.md +67 -2
- package/THIRD_PARTY_NOTICES.md +225 -3
- package/dist/artifacts.d.ts +6 -2
- package/dist/artifacts.js +17 -2
- package/dist/{chunk-IR22ZTQW.js → chunk-QYU4OW5A.js} +481 -27
- package/dist/{chunk-2QXKVQ6O.js → chunk-WJOF6USY.js} +47 -6
- package/dist/devtools-browser.js +16914 -0
- package/dist/devtools-observer.js +95 -0
- package/dist/fonts.d.ts +1 -1
- package/dist/icons-BdOehQmB.d.ts +424 -0
- package/dist/icons.d.ts +3 -339
- package/dist/icons.js +5 -1
- package/dist/{preview-assets-MVUJJ5XR.js → preview-assets-KGUICQHG.js} +6 -0
- package/dist/preview.d.ts +1 -1
- package/dist/preview.js +250 -108
- package/dist/server.d.ts +11 -4
- package/dist/server.js +1843 -280
- package/docs/beacon-map-audits.md +145 -0
- package/docs/bounded-vector-feature-inspection.md +3 -4
- package/docs/layers-inspection.md +92 -0
- package/docs/local-comparison-workbench-and-compiler-inspection.md +74 -171
- package/docs/map-design-diagnostics.md +76 -0
- package/docs/native-artifacts.md +3 -3
- package/package.json +7 -5
- package/ui-fonts/hubot-sans-OFL.txt +93 -0
- package/ui-fonts/hubot-sans.woff2 +0 -0
- package/ui-fonts/inter-OFL.txt +92 -0
- package/ui-fonts/inter.woff2 +0 -0
- package/ui-fonts/jetbrains-mono-OFL.txt +93 -0
- package/ui-fonts/jetbrains-mono.woff2 +0 -0
- package/ui-fonts/mona-sans-OFL.txt +93 -0
- package/ui-fonts/mona-sans.woff2 +0 -0
package/dist/preview.js
CHANGED
|
@@ -140,11 +140,101 @@ function normalizeTileflowLandmarkManifest(candidate, manifestUrl) {
|
|
|
140
140
|
};
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
// src/preview-scene.ts
|
|
144
|
+
var previewSceneClient = String.raw`
|
|
145
|
+
let sceneStyleRevision = 0;
|
|
146
|
+
globalThis.__tileflowApplyScene = async (serialized, isCurrent) => {
|
|
147
|
+
const next = JSON.parse(serialized);
|
|
148
|
+
if (next.style) {
|
|
149
|
+
// getStyle() retains renderer instrumentation on sources. It is not authored
|
|
150
|
+
// style-spec input; MapLibre adds it back from the map's timing option.
|
|
151
|
+
delete next.style.collectResourceTiming;
|
|
152
|
+
for (const source of Object.values(next.style.sources || {})) delete source.collectResourceTiming;
|
|
153
|
+
await loadTileflowStyleFonts(next.style, {fontFaces: previewFontFaces});
|
|
154
|
+
}
|
|
155
|
+
if (!isCurrent()) return false;
|
|
156
|
+
const context = map.getCanvas().getContext("webgl2") || map.getCanvas().getContext("webgl");
|
|
157
|
+
if (context?.isContextLost()) throw new Error("The map's graphics context is recovering. Try the change again.");
|
|
158
|
+
if (next.style) {
|
|
159
|
+
// MapLibre ignores metadata-only diffs. A global-state revision also commits the
|
|
160
|
+
// new 3D design metadata, without replacing sources, tiles or custom layers.
|
|
161
|
+
next.style.state = {...next.style.state, "tileflow:preview-revision": ++sceneStyleRevision};
|
|
162
|
+
const layerOrder = map.getLayersOrder();
|
|
163
|
+
map.setStyle(next.style, {diff:true});
|
|
164
|
+
// Custom layers are omitted from the serialized style. A diff can reinsert the
|
|
165
|
+
// surrounding style layers above them, covering 3D with the flat map. Restore
|
|
166
|
+
// their placement before the nearest surviving successor, without remounting.
|
|
167
|
+
let before;
|
|
168
|
+
for (let index = layerOrder.length - 1; index >= 0; index--) {
|
|
169
|
+
const id = layerOrder[index], layer = map.getLayer(id);
|
|
170
|
+
if (!layer) continue;
|
|
171
|
+
if (layer.type === "custom") map.moveLayer(id, before);
|
|
172
|
+
before = id;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
previewSearchParameters.set("animation", String(next.animation));
|
|
176
|
+
previewSearchParameters.delete("trees3d");
|
|
177
|
+
const visibleTrees = map.getStyle()?.metadata?.["tileflow:vegetation-display"]?.visible ?? true;
|
|
178
|
+
threeDimensionalControl?.setEnabled(next.buildings3d);
|
|
179
|
+
treeControl?.setEnabled(visibleTrees);
|
|
180
|
+
mountCoreScene(map);
|
|
181
|
+
treeControl?.update();
|
|
182
|
+
updateGlobeBackdrop(map);
|
|
183
|
+
const url = new URL(location.href);
|
|
184
|
+
url.searchParams.delete("scene");
|
|
185
|
+
url.searchParams.set("map", next.map);
|
|
186
|
+
url.searchParams.set("theme", next.theme);
|
|
187
|
+
if (Object.keys(next.controls).length) url.searchParams.set("controls", JSON.stringify(next.controls));
|
|
188
|
+
else url.searchParams.delete("controls");
|
|
189
|
+
if (next.animation) url.searchParams.set("animation", String(next.animation));
|
|
190
|
+
else url.searchParams.delete("animation");
|
|
191
|
+
history.replaceState(history.state, "", url.href);
|
|
192
|
+
// Read the current camera here; a slow compile must never undo navigation.
|
|
193
|
+
writeCameraToUrl(map);
|
|
194
|
+
map.triggerRepaint();
|
|
195
|
+
return true;
|
|
196
|
+
};
|
|
197
|
+
`;
|
|
198
|
+
|
|
199
|
+
// src/preview-scene-themes.ts
|
|
200
|
+
function createPreviewSceneThemes(map, readSun) {
|
|
201
|
+
const cache = {
|
|
202
|
+
value: void 0,
|
|
203
|
+
snapshot() {
|
|
204
|
+
if (!cache.value) {
|
|
205
|
+
const metadata = map.getStyle()?.metadata;
|
|
206
|
+
cache.value = { metadata, sun: readSun(metadata) };
|
|
207
|
+
}
|
|
208
|
+
return cache.value;
|
|
209
|
+
},
|
|
210
|
+
invalidate() {
|
|
211
|
+
cache.value = void 0;
|
|
212
|
+
},
|
|
213
|
+
release() {
|
|
214
|
+
map.off("styledata", cache.invalidate);
|
|
215
|
+
map.off("style.load", cache.invalidate);
|
|
216
|
+
map.off("remove", cache.release);
|
|
217
|
+
cache.invalidate();
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
map.on("styledata", cache.invalidate);
|
|
221
|
+
map.on("style.load", cache.invalidate);
|
|
222
|
+
map.on("remove", cache.release);
|
|
223
|
+
return {
|
|
224
|
+
getSun() {
|
|
225
|
+
return cache.snapshot().sun;
|
|
226
|
+
},
|
|
227
|
+
getThemeMetadata(key) {
|
|
228
|
+
return { values: [cache.snapshot().metadata?.[key]], position: 0 };
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
143
233
|
// src/preview-html.ts
|
|
144
|
-
function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticPreview, fontFaces = []) {
|
|
145
|
-
const styleUrl = preview ? `${basePath}/styles/${preview.mapName}/${preview.themeName}.json` : void 0;
|
|
234
|
+
function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticPreview, fontFaces = [], fitViewport = false, controls) {
|
|
235
|
+
const styleUrl = preview ? `${basePath}/styles/${preview.mapName}/${preview.themeName}.json${controls ? `?${new URLSearchParams({ controls })}` : ""}` : void 0;
|
|
146
236
|
const mapOptions = preview ? previewMapOptions(preview) : void 0;
|
|
147
|
-
const viewportCss = preview?.viewport ? `
|
|
237
|
+
const viewportCss = preview?.viewport && !fitViewport ? `
|
|
148
238
|
html, body { min-height: 100%; margin: 0; }
|
|
149
239
|
body {
|
|
150
240
|
display: grid;
|
|
@@ -168,7 +258,6 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
168
258
|
${viewportCss}
|
|
169
259
|
body { font-family: ui-sans-serif, system-ui, sans-serif; }
|
|
170
260
|
#map {
|
|
171
|
-
--tileflow-globe-radius: 220px;
|
|
172
261
|
background-color: var(--tileflow-space, #2F5070);
|
|
173
262
|
background-image:
|
|
174
263
|
radial-gradient(circle at 18% 24%, rgba(225, 240, 250, 0.34) 0 1px, transparent 1.4px),
|
|
@@ -184,22 +273,8 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
184
273
|
radial-gradient(circle at 72% 62%, rgba(225, 240, 250, 0.22) 0 1px, transparent 1.3px),
|
|
185
274
|
radial-gradient(circle at 42% 78%, rgba(225, 240, 250, 0.18) 0 0.8px, transparent 1.2px);
|
|
186
275
|
}
|
|
187
|
-
#map.tileflow-globe
|
|
188
|
-
|
|
189
|
-
position: absolute;
|
|
190
|
-
z-index: 0;
|
|
191
|
-
left: 50%;
|
|
192
|
-
top: 50%;
|
|
193
|
-
width: calc(var(--tileflow-globe-radius) * 2);
|
|
194
|
-
height: calc(var(--tileflow-globe-radius) * 2);
|
|
195
|
-
border: 1px solid var(--tileflow-rim-line, rgba(248, 252, 255, 0.88));
|
|
196
|
-
border-radius: 50%;
|
|
197
|
-
box-shadow:
|
|
198
|
-
0 0 9px 3px var(--tileflow-rim-core, rgba(248, 252, 255, 0.9)),
|
|
199
|
-
0 0 28px 10px var(--tileflow-rim-glow, rgba(176, 220, 246, 0.52)),
|
|
200
|
-
0 0 58px 20px var(--tileflow-rim-haze, rgba(132, 195, 232, 0.2));
|
|
201
|
-
pointer-events: none;
|
|
202
|
-
transform: translate(-50%, -50%);
|
|
276
|
+
#map.tileflow-globe .maplibregl-canvas {
|
|
277
|
+
filter: var(--tileflow-globe-rim, none);
|
|
203
278
|
}
|
|
204
279
|
#map.tileflow-globe > .maplibregl-canvas-container {
|
|
205
280
|
position: relative;
|
|
@@ -233,6 +308,7 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
233
308
|
padding: 12px;
|
|
234
309
|
white-space: pre-wrap;
|
|
235
310
|
}
|
|
311
|
+
html[data-devtools-embedded=true] .status { display:none !important; }
|
|
236
312
|
.maplibregl-ctrl-group .tileflow-3d-toggle,
|
|
237
313
|
.maplibregl-ctrl-group .tileflow-tree-toggle,
|
|
238
314
|
.maplibregl-ctrl-group .tileflow-theme-toggle {
|
|
@@ -253,6 +329,12 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
253
329
|
.maplibregl-ctrl-group .tileflow-theme-toggle:hover {
|
|
254
330
|
background-color: #F1F3F4;
|
|
255
331
|
}
|
|
332
|
+
.maplibregl-ctrl-group { background:#faf6eb; border:1px solid #e0d5c4; border-radius:3px; box-shadow:0 3px 12px #30251f18; }
|
|
333
|
+
.maplibregl-ctrl-group button { color:#30251f; }
|
|
334
|
+
.maplibregl-ctrl-group button + button { border-top-color:#e0d5c4; }
|
|
335
|
+
.maplibregl-ctrl-group .tileflow-3d-toggle,.maplibregl-ctrl-group .tileflow-tree-toggle,.maplibregl-ctrl-group .tileflow-theme-toggle { color:#30251f; font-weight:500; }
|
|
336
|
+
.maplibregl-ctrl-group .tileflow-3d-toggle[aria-pressed="true"],.maplibregl-ctrl-group .tileflow-tree-toggle[aria-pressed="true"] { background:#f0dfe0; color:#8c2e3f; }
|
|
337
|
+
.maplibregl-ctrl-group .tileflow-3d-toggle:hover,.maplibregl-ctrl-group .tileflow-tree-toggle:hover,.maplibregl-ctrl-group .tileflow-theme-toggle:hover { background:#eee4d3; }
|
|
256
338
|
</style>
|
|
257
339
|
</head>
|
|
258
340
|
<body>
|
|
@@ -262,19 +344,25 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
262
344
|
<script type="importmap">{"imports":{"fflate":"${basePath}/__runtime/fflate.js","three":"${basePath}/__runtime/three.module.js"}}</script>
|
|
263
345
|
<script type="module">
|
|
264
346
|
import * as maplibregl from "${basePath}/__runtime/maplibre-gl.mjs";
|
|
347
|
+
import {createPreviewObserver} from "${basePath}/__runtime/devtools-observer.js";
|
|
265
348
|
import {loadTileflowStyleFonts} from "${basePath}/__runtime/tileflow-browser.js";
|
|
266
349
|
import {registerTileflowContourProtocol} from "${basePath}/__runtime/tileflow-browser.js";
|
|
267
350
|
import {registerTileflowPmtilesProtocol} from "${basePath}/__runtime/tileflow-browser.js";
|
|
268
|
-
import {createTileflowBuildings3D, createTileflowStreetLights,
|
|
351
|
+
import {createTileflowBuildings3D, createTileflowStreetLights, createTileflowVegetation} from "${basePath}/__runtime/tileflow-browser.js";
|
|
269
352
|
import {readTileflowSunPreset, resolveTileflowSun, tileflowDefaultSunPreset} from "${basePath}/__runtime/tileflow-browser.js";
|
|
270
353
|
|
|
271
354
|
maplibregl.setWorkerUrl("${basePath}/__runtime/maplibre-gl-worker.mjs");
|
|
272
355
|
|
|
356
|
+
const devtoolsObserver = window.parent !== window ? createPreviewObserver() : undefined;
|
|
357
|
+
globalThis.__tileflowDevtoolsObserver = devtoolsObserver;
|
|
358
|
+
if (window.parent !== window) document.documentElement.dataset.devtoolsEmbedded = "true";
|
|
359
|
+
|
|
273
360
|
const initialStatus = ${JSON.stringify(initialStatus)};
|
|
274
361
|
const normalizeTileflowLandmarkManifest = ${normalizeTileflowLandmarkManifest.toString()};
|
|
275
362
|
const readBoundedTileflowResponse = ${readBoundedTileflowResponse.toString()};
|
|
276
363
|
const readBoundedTileflowJsonResponse = ${readBoundedTileflowJsonResponse.toString()};
|
|
277
364
|
const initialGeneration = initialStatus.generation;
|
|
365
|
+
document.documentElement.dataset.tileflowArtifactGeneration = String(initialStatus.lastGoodGeneration ?? initialGeneration);
|
|
278
366
|
const badge = document.getElementById("badge");
|
|
279
367
|
const status = document.getElementById("status");
|
|
280
368
|
const previewLabel = ${JSON.stringify(preview?.label)};
|
|
@@ -318,8 +406,8 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
318
406
|
if (values[0] === "off") return false;
|
|
319
407
|
return fallback;
|
|
320
408
|
}
|
|
321
|
-
let threeDimensionalEnabled = readToggleFromUrl("buildings3d",
|
|
322
|
-
let treesEnabled = readToggleFromUrl("trees3d",
|
|
409
|
+
let threeDimensionalEnabled = readToggleFromUrl("buildings3d", true);
|
|
410
|
+
let treesEnabled = readToggleFromUrl("trees3d", true);
|
|
323
411
|
const landmarkState = {
|
|
324
412
|
active: [],
|
|
325
413
|
cacheLimit: undefined,
|
|
@@ -940,14 +1028,7 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
940
1028
|
return;
|
|
941
1029
|
}
|
|
942
1030
|
|
|
943
|
-
// A Web Mercator world is 512 * 2^zoom pixels wide. Dividing its
|
|
944
|
-
// circumference by 2\u03C0 closely tracks MapLibre's on-screen globe radius.
|
|
945
|
-
const radius = Math.min(
|
|
946
|
-
4096,
|
|
947
|
-
((512 * Math.pow(2, map.getZoom())) / (2 * Math.PI)) * 0.91
|
|
948
|
-
);
|
|
949
1031
|
container.classList.add("tileflow-globe");
|
|
950
|
-
container.style.setProperty("--tileflow-globe-radius", radius.toFixed(2) + "px");
|
|
951
1032
|
applyAtmosphereBackdrop(map, container);
|
|
952
1033
|
}
|
|
953
1034
|
|
|
@@ -961,12 +1042,16 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
961
1042
|
const value = Number.parseInt(match[1], 16);
|
|
962
1043
|
return "rgba(" + (value >> 16) + ", " + ((value >> 8) & 255) + ", " + (value & 255) + ", " + alpha + ")";
|
|
963
1044
|
};
|
|
1045
|
+
// Use the rendered canvas alpha, as the shared browser atmosphere does.
|
|
1046
|
+
// A zoom-only circle cannot track the globe's latitude, viewport or pitch.
|
|
1047
|
+
const opacity = Math.max(0, Math.min(1, (6 - map.getZoom()) / 3));
|
|
1048
|
+
const core = rgba(atmosphere?.horizonColor ?? "#F8FCFF", opacity * 0.65);
|
|
1049
|
+
const glow = rgba(atmosphere?.horizonColor ?? "#B0DCF6", opacity * 0.4);
|
|
964
1050
|
const properties = {
|
|
965
1051
|
"--tileflow-space": rgba(atmosphere?.spaceColor, 1),
|
|
966
|
-
"--tileflow-rim
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
"--tileflow-rim-haze": rgba(atmosphere?.skyColor, 0.35),
|
|
1052
|
+
"--tileflow-globe-rim": opacity > 0
|
|
1053
|
+
? "drop-shadow(0 0 2px " + core + ") drop-shadow(0 0 12px " + glow + ")"
|
|
1054
|
+
: "none",
|
|
970
1055
|
};
|
|
971
1056
|
for (const [name, value] of Object.entries(properties)) {
|
|
972
1057
|
if (value) container.style.setProperty(name, value);
|
|
@@ -1921,6 +2006,7 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
1921
2006
|
(layer) =>
|
|
1922
2007
|
(layer.id.startsWith("tileflow-label-") &&
|
|
1923
2008
|
!layer.id.startsWith("tileflow-label-road-")) ||
|
|
2009
|
+
layer.id === "tileflow-poi" ||
|
|
1924
2010
|
layer.id.startsWith("tileflow-poi-")
|
|
1925
2011
|
)?.id;
|
|
1926
2012
|
const treeLayer = map.getLayer("tileflow-vegetation-trees")
|
|
@@ -1930,48 +2016,82 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
1930
2016
|
}
|
|
1931
2017
|
|
|
1932
2018
|
// Core's 3D layers, as an application mounts them: the map's style describes how its
|
|
1933
|
-
// buildings, trees and street lights look, and the controls only show and hide them.
|
|
1934
|
-
//
|
|
2019
|
+
// buildings, trees and street lights look, and the controls only show and hide them. The
|
|
2020
|
+
// vegetation control hides all trees when off. The preview shows one theme at a
|
|
1935
2021
|
// time, so the style on screen holds the sun and the design.
|
|
1936
|
-
const coreScene = {buildings: undefined, trees: undefined, lights: undefined, buildingsShown:
|
|
2022
|
+
const coreScene = {buildings: undefined, trees: undefined, lights: undefined, buildingsShown: undefined, animation: "0"};
|
|
2023
|
+
const runtimeLayerVisibility = {};
|
|
1937
2024
|
globalThis.__tileflowPreviewScene = coreScene;
|
|
2025
|
+
globalThis.__tileflowRuntimeVisibility = (id, visible) => {
|
|
2026
|
+
if (!['tileflow-buildings-3d','tileflow-trees-3d','tileflow-street-lights'].includes(id)) return undefined;
|
|
2027
|
+
if (typeof visible === 'boolean') { runtimeLayerVisibility[id]=visible; mountCoreScene(map); map.triggerRepaint(); }
|
|
2028
|
+
return runtimeLayerVisibility[id] !== false;
|
|
2029
|
+
};
|
|
2030
|
+
// Explicit observation capability: controllers stay private to the preview and expose
|
|
2031
|
+
// only their public state/statistics to the parent inspector.
|
|
2032
|
+
globalThis.__tileflowInspectModels = () => [...(coreScene.buildings?.inspectModels?.() || []), ...(coreScene.trees?.inspectModels?.() || [])];
|
|
2033
|
+
globalThis.__tileflowDescribeRuntimeLayers = () => ({
|
|
2034
|
+
...(coreScene.buildings ? {"tileflow-buildings-3d": {controller:"Buildings 3D", state:{visible:runtimeLayerVisibility["tileflow-buildings-3d"] !== false, shown:coreScene.buildingsShown, scale:coreScene.buildings.scale}, stats:coreScene.buildings.stats}} : {}),
|
|
2035
|
+
...(coreScene.trees ? {"tileflow-trees-3d": {controller:"Vegetation", state:{visible:runtimeLayerVisibility["tileflow-trees-3d"] !== false, representation:coreScene.trees.representation}, stats:coreScene.trees.stats}} : {}),
|
|
2036
|
+
...(coreScene.lights ? {"tileflow-street-lights": {controller:"Street lights", state:{visible:runtimeLayerVisibility["tileflow-street-lights"] !== false, mounted:true}, stats:coreScene.lights.stats}} : {})
|
|
2037
|
+
});
|
|
2038
|
+
const createPreviewSceneThemes = ${createPreviewSceneThemes.toString()};
|
|
2039
|
+
const coreSceneThemeControllers = new WeakMap();
|
|
1938
2040
|
function coreSceneThemes(map) {
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
}
|
|
1947
|
-
};
|
|
2041
|
+
let controller = coreSceneThemeControllers.get(map);
|
|
2042
|
+
if (!controller) {
|
|
2043
|
+
controller = createPreviewSceneThemes(map, metadata =>
|
|
2044
|
+
resolveTileflowSun(readTileflowSunPreset(metadata) ?? tileflowDefaultSunPreset));
|
|
2045
|
+
coreSceneThemeControllers.set(map, controller);
|
|
2046
|
+
}
|
|
2047
|
+
return controller;
|
|
1948
2048
|
}
|
|
2049
|
+
let mountingCoreScene = false;
|
|
1949
2050
|
function mountCoreScene(map) {
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
coreScene.
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
2051
|
+
if (mountingCoreScene) return;
|
|
2052
|
+
mountingCoreScene = true;
|
|
2053
|
+
try {
|
|
2054
|
+
const metadata = map.getStyle()?.metadata ?? {};
|
|
2055
|
+
const controller = coreSceneThemes(map);
|
|
2056
|
+
if (!coreScene.buildings && metadata["tileflow:buildings"]) {
|
|
2057
|
+
coreScene.buildings = createTileflowBuildings3D({map, controller});
|
|
2058
|
+
}
|
|
2059
|
+
if (!coreScene.trees) {
|
|
2060
|
+
if (!previewSearchParameters.has("trees3d")) {
|
|
2061
|
+
treesEnabled = metadata["tileflow:vegetation-display"]?.visible ?? true;
|
|
2062
|
+
}
|
|
2063
|
+
coreScene.trees = createTileflowVegetation({map, controller, visible: treesEnabled,
|
|
2064
|
+
viewMode: threeDimensionalEnabled ? "3d" : "2d", transformRequest: previewTransformRequest});
|
|
2065
|
+
}
|
|
2066
|
+
coreScene.trees.setVisible(treesEnabled && runtimeLayerVisibility["tileflow-trees-3d"] !== false);
|
|
2067
|
+
coreScene.trees.setViewMode(threeDimensionalEnabled ? "3d" : "2d");
|
|
2068
|
+
// Street lights require an explicit map opt-in and the 3D control; defaults stay off.
|
|
2069
|
+
if (!coreScene.lights && threeDimensionalEnabled && metadata["tileflow:street-lights"]) {
|
|
2070
|
+
coreScene.lights = createTileflowStreetLights({map, controller});
|
|
2071
|
+
} else if (coreScene.lights && (!threeDimensionalEnabled || !metadata["tileflow:street-lights"])) {
|
|
2072
|
+
coreScene.lights.dispose();
|
|
2073
|
+
coreScene.lights = undefined;
|
|
2074
|
+
}
|
|
2075
|
+
const lightLayer = map.getLayer("tileflow-street-lights");
|
|
2076
|
+
const lightRenderer = lightLayer?.implementation || lightLayer;
|
|
2077
|
+
if (lightRenderer?.render && !lightRenderer.__previewVisibility) {
|
|
2078
|
+
const render = lightRenderer.render;
|
|
2079
|
+
lightRenderer.render = function(...args) {
|
|
2080
|
+
if (runtimeLayerVisibility["tileflow-street-lights"] !== false) return render.apply(this,args);
|
|
2081
|
+
};
|
|
2082
|
+
lightRenderer.__previewVisibility = true;
|
|
2083
|
+
}
|
|
2084
|
+
const showBuildings = threeDimensionalEnabled && runtimeLayerVisibility["tileflow-buildings-3d"] !== false;
|
|
2085
|
+
const instant = {transition: {duration: 0}};
|
|
2086
|
+
const animation = previewSearchParameters.get('animation') || '0';
|
|
2087
|
+
const duration = animation === 'fade' ? 800 : Math.min(3000, Math.max(0, Number(animation) || 0));
|
|
2088
|
+
// Only a change of the controls moves the layers, so a style event does not restart them.
|
|
2089
|
+
if (coreScene.buildings && (coreScene.buildingsShown !== showBuildings || coreScene.animation !== animation)) {
|
|
2090
|
+
coreScene.buildingsShown = showBuildings;
|
|
2091
|
+
coreScene.animation = animation;
|
|
2092
|
+
void (showBuildings ? coreScene.buildings.show({transition: {duration, mode: animation === "fade" ? "fade" : "grow"}}) : coreScene.buildings.hide(instant));
|
|
2093
|
+
}
|
|
2094
|
+
} finally {mountingCoreScene = false;}
|
|
1975
2095
|
}
|
|
1976
2096
|
|
|
1977
2097
|
class ThreeDimensionalControl {
|
|
@@ -2076,18 +2196,11 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
2076
2196
|
}
|
|
2077
2197
|
|
|
2078
2198
|
update() {
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
);
|
|
2082
|
-
// Core's 3D trees hide the style's flat trees while they stand (mountCoreScene).
|
|
2083
|
-
if (treeLayer && !this.map.getStyle()?.metadata?.["tileflow:vegetation"]) {
|
|
2084
|
-
if (this.map.getLayoutProperty(treeLayer.id, "visibility") !== "visible") {
|
|
2085
|
-
this.map.setLayoutProperty(treeLayer.id, "visibility", "visible");
|
|
2086
|
-
}
|
|
2087
|
-
}
|
|
2199
|
+
if (this.map.isStyleLoaded()) mountCoreScene(this.map);
|
|
2200
|
+
this.enabled = treesEnabled;
|
|
2088
2201
|
this.button.textContent = this.enabled ? "TREES ON" : "TREES OFF";
|
|
2089
2202
|
this.button.setAttribute("aria-pressed", String(this.enabled));
|
|
2090
|
-
this.button.title = this.enabled ? "
|
|
2203
|
+
this.button.title = this.enabled ? "Hide vegetation" : "Show vegetation";
|
|
2091
2204
|
}
|
|
2092
2205
|
|
|
2093
2206
|
onRemove() {
|
|
@@ -2183,24 +2296,21 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
2183
2296
|
...visibleLayerGroups,
|
|
2184
2297
|
].join(",");
|
|
2185
2298
|
}
|
|
2299
|
+
const vegetationLayers = map.getStyle()?.metadata?.["tileflow:vegetation-display"]?.flatLayers;
|
|
2186
2300
|
for (const layer of map.getStyle()?.layers || []) {
|
|
2187
|
-
if (!defaultLayerVisibility.has(layer.id)) {
|
|
2188
|
-
defaultLayerVisibility.set(
|
|
2189
|
-
layer.id,
|
|
2190
|
-
map.getLayoutProperty(layer.id, "visibility") || "visible"
|
|
2191
|
-
);
|
|
2192
|
-
}
|
|
2193
2301
|
if (layer.type === "symbol" && layer.layout?.["text-field"] !== undefined) {
|
|
2194
|
-
|
|
2302
|
+
const hideLabels = !visibleLayerGroups.has("labels");
|
|
2303
|
+
if (hideLabels && !defaultLayerTextField.has(layer.id)) {
|
|
2195
2304
|
defaultLayerTextField.set(
|
|
2196
2305
|
layer.id,
|
|
2197
2306
|
map.getLayoutProperty(layer.id, "text-field")
|
|
2198
2307
|
);
|
|
2199
2308
|
}
|
|
2200
|
-
const
|
|
2201
|
-
|
|
2202
|
-
|
|
2309
|
+
const restoreText = !hideLabels && defaultLayerTextField.has(layer.id);
|
|
2310
|
+
const textField = hideLabels ? "" : defaultLayerTextField.get(layer.id);
|
|
2311
|
+
if (restoreText) defaultLayerTextField.delete(layer.id);
|
|
2203
2312
|
if (
|
|
2313
|
+
(hideLabels || restoreText) &&
|
|
2204
2314
|
!previewLayoutValuesEqual(
|
|
2205
2315
|
map.getLayoutProperty(layer.id, "text-field"),
|
|
2206
2316
|
textField
|
|
@@ -2209,21 +2319,29 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
2209
2319
|
map.setLayoutProperty(layer.id, "text-field", textField);
|
|
2210
2320
|
}
|
|
2211
2321
|
}
|
|
2322
|
+
// The shared vegetation controller owns every flat representation.
|
|
2323
|
+
if (vegetationLayers?.some((entry) => entry.id === layer.id.split("::")[0])) continue;
|
|
2212
2324
|
const groups = previewLayerGroups(layer);
|
|
2325
|
+
const hideLayer = groups.some((group) => !visibleLayerGroups.has(group));
|
|
2326
|
+
// Group controls only own the properties they mask. Ordinary style events must
|
|
2327
|
+
// preserve temporary DevTools edits, including visibility and label text.
|
|
2328
|
+
if (hideLayer && !defaultLayerVisibility.has(layer.id)) {
|
|
2329
|
+
defaultLayerVisibility.set(layer.id, map.getLayoutProperty(layer.id, "visibility") || "visible");
|
|
2330
|
+
}
|
|
2331
|
+
if (!hideLayer && !defaultLayerVisibility.has(layer.id)) continue;
|
|
2213
2332
|
const threeDimensionalToggle = layer.metadata?.["tileflow:3d-toggle"];
|
|
2214
2333
|
const authoredVisibility = defaultLayerVisibility.get(layer.id);
|
|
2334
|
+
if (!hideLayer) defaultLayerVisibility.delete(layer.id);
|
|
2215
2335
|
const styleMetadata = map.getStyle()?.metadata ?? {};
|
|
2216
2336
|
// Core's 3D buildings and trees stand in for compiled extrusions and flat trees.
|
|
2217
|
-
const flatTrees = styleMetadata["tileflow:vegetation"]?.flatLayers ?? [];
|
|
2218
2337
|
const replacedByCore =
|
|
2219
|
-
|
|
2220
|
-
(treesEnabled && flatTrees.includes(layer.id.split("::")[0]));
|
|
2338
|
+
threeDimensionalToggle === "building" && styleMetadata["tileflow:buildings"];
|
|
2221
2339
|
const toggleVisibility = replacedByCore
|
|
2222
2340
|
? "none"
|
|
2223
2341
|
: threeDimensionalToggle === "building" || threeDimensionalToggle === "landmark"
|
|
2224
2342
|
? (threeDimensionalEnabled ? "visible" : "none")
|
|
2225
2343
|
: authoredVisibility;
|
|
2226
|
-
const visibility =
|
|
2344
|
+
const visibility = hideLayer
|
|
2227
2345
|
? "none"
|
|
2228
2346
|
: toggleVisibility;
|
|
2229
2347
|
if (map.getLayoutProperty(layer.id, "visibility") !== visibility) {
|
|
@@ -2238,6 +2356,16 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
2238
2356
|
map.fire("tileflow:visible-layer-groups", {groups: [...visibleLayerGroups]});
|
|
2239
2357
|
}
|
|
2240
2358
|
|
|
2359
|
+
function previewTransformRequest(url) {
|
|
2360
|
+
try {
|
|
2361
|
+
return new URL(url, location.href).hostname === "dev-tiles.tileflow.dev"
|
|
2362
|
+
? {credentials: "include", url}
|
|
2363
|
+
: {url};
|
|
2364
|
+
} catch {
|
|
2365
|
+
return {url};
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2241
2369
|
if (styleUrl) {
|
|
2242
2370
|
// Dense vector tiles finish in bursts with multiple workers, causing several
|
|
2243
2371
|
// bucket uploads in one frame. One worker keeps navigation smooth; benchmarks
|
|
@@ -2248,23 +2376,16 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
2248
2376
|
registerTileflowContourProtocol({addProtocol: maplibregl.addProtocol});
|
|
2249
2377
|
registerTileflowPmtilesProtocol({addProtocol: maplibregl.addProtocol});
|
|
2250
2378
|
const map = new maplibregl.Map({
|
|
2251
|
-
collectResourceTiming: mapBenchmarkEnabled,
|
|
2379
|
+
collectResourceTiming: mapBenchmarkEnabled || Boolean(devtoolsObserver),
|
|
2252
2380
|
container: "map",
|
|
2253
2381
|
style: styleUrl,
|
|
2254
|
-
transformRequest:
|
|
2255
|
-
try {
|
|
2256
|
-
return new URL(url, location.href).hostname === "dev-tiles.tileflow.dev"
|
|
2257
|
-
? {credentials: "include", url}
|
|
2258
|
-
: {url};
|
|
2259
|
-
} catch {
|
|
2260
|
-
return {url};
|
|
2261
|
-
}
|
|
2262
|
-
},
|
|
2382
|
+
transformRequest: previewTransformRequest,
|
|
2263
2383
|
...resolveInitialMapOptions(previewMapOptions)
|
|
2264
2384
|
});
|
|
2265
2385
|
globalThis.__tileflowPreviewMap = map;
|
|
2386
|
+
devtoolsObserver?.attach(map);
|
|
2266
2387
|
installMapBenchmark(map);
|
|
2267
|
-
map.addControl(new maplibregl.NavigationControl(), "top-right");
|
|
2388
|
+
if (window.parent === window) map.addControl(new maplibregl.NavigationControl(), "top-right");
|
|
2268
2389
|
let threeDimensionalControl;
|
|
2269
2390
|
let treeControl;
|
|
2270
2391
|
if (isSemanticPreview) {
|
|
@@ -2272,8 +2393,16 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
2272
2393
|
treeControl = new TreeControl();
|
|
2273
2394
|
map.addControl(threeDimensionalControl, "top-right");
|
|
2274
2395
|
map.addControl(treeControl, "top-right");
|
|
2396
|
+
// Embedded Map DevTools owns the common Controls sidebar. Keep the controls mounted
|
|
2397
|
+
// for their renderer lifecycle and message bridge, but remove duplicate buttons.
|
|
2398
|
+
if (window.parent !== window) {
|
|
2399
|
+
threeDimensionalControl.container.hidden = true;
|
|
2400
|
+
treeControl.container.hidden = true;
|
|
2401
|
+
}
|
|
2275
2402
|
}
|
|
2276
|
-
|
|
2403
|
+
// Map DevTools has a shared theme selector in its Controls panel; keep the map-level
|
|
2404
|
+
// cycling button for standalone previews only.
|
|
2405
|
+
if (previewThemes.length > 1 && window.parent === window) map.addControl(new ThemeControl(), "top-right");
|
|
2277
2406
|
let ensuringThreeDimensionalLayers;
|
|
2278
2407
|
const ensureThreeDimensionalLayers = () => {
|
|
2279
2408
|
if (ensuringThreeDimensionalLayers) return ensuringThreeDimensionalLayers;
|
|
@@ -2360,13 +2489,16 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
2360
2489
|
map.on("tileflow:3d-toggle", ensureThreeDimensionalLayers);
|
|
2361
2490
|
map.on("tileflow:trees-toggle", ensureThreeDimensionalLayers);
|
|
2362
2491
|
map.on("load", () => {
|
|
2492
|
+
mountCoreScene(map);
|
|
2493
|
+
treeControl?.update();
|
|
2363
2494
|
writeCameraToUrl(map);
|
|
2364
2495
|
updateGlobeBackdrop(map);
|
|
2365
2496
|
ensureThreeDimensionalLayers();
|
|
2366
|
-
mountCoreScene(map);
|
|
2367
2497
|
});
|
|
2368
2498
|
map.on("move", () => updateGlobeBackdrop(map));
|
|
2499
|
+
map.on("style.load", () => updateGlobeBackdrop(map));
|
|
2369
2500
|
map.on("moveend", () => writeCameraToUrl(map));
|
|
2501
|
+
${previewSceneClient}
|
|
2370
2502
|
addEventListener("message", (event) => {
|
|
2371
2503
|
if (
|
|
2372
2504
|
window.parent === window ||
|
|
@@ -2437,6 +2569,15 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
2437
2569
|
}
|
|
2438
2570
|
|
|
2439
2571
|
applyStatus(initialStatus);
|
|
2572
|
+
// Embedded previews share their host's stream. Two streams per tab can exhaust Chrome's
|
|
2573
|
+
// six HTTP/1 connections and leave even local module/font requests waiting indefinitely.
|
|
2574
|
+
let unsubscribePreviewStatus;
|
|
2575
|
+
try {
|
|
2576
|
+
if (window.parent !== window) unsubscribePreviewStatus = window.parent.__tileflowDevtoolsHost?.subscribePreviewStatus?.(window, applyStatus);
|
|
2577
|
+
} catch { /* Other embedders keep the standalone stream. */ }
|
|
2578
|
+
if (unsubscribePreviewStatus) {
|
|
2579
|
+
addEventListener("beforeunload", unsubscribePreviewStatus);
|
|
2580
|
+
} else {
|
|
2440
2581
|
const events = new EventSource(${JSON.stringify(`${basePath}/__events`)});
|
|
2441
2582
|
// Generations are monotonic only within one dev-server process. A source-code watcher can
|
|
2442
2583
|
// restart that process at generation 1, so reconnecting after a live connection was lost
|
|
@@ -2457,6 +2598,7 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
2457
2598
|
for (const eventName of ["building", "ready", "invalid"]) {
|
|
2458
2599
|
events.addEventListener(eventName, (event) => applyStatus(JSON.parse(event.data)));
|
|
2459
2600
|
}
|
|
2601
|
+
}
|
|
2460
2602
|
</script>
|
|
2461
2603
|
</body>
|
|
2462
2604
|
</html>`;
|
package/dist/server.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import '@tileflow/core';
|
|
|
5
5
|
import '@tileflow/core/build';
|
|
6
6
|
import '@tileflow/core/manifest';
|
|
7
7
|
import '@tileflow/core/native-profile';
|
|
8
|
-
import './icons.js';
|
|
8
|
+
import './icons-BdOehQmB.js';
|
|
9
9
|
import './config.js';
|
|
10
10
|
|
|
11
11
|
declare const tileflowComparisonSchemaVersion: 1;
|
|
@@ -23,16 +23,21 @@ type TileflowComparisonSide = {
|
|
|
23
23
|
/** Same-origin preview URL below basePath, including its map/theme query. */
|
|
24
24
|
previewUrl: string;
|
|
25
25
|
};
|
|
26
|
-
type TileflowComparisonMode = 'blink' | 'overlay' | 'side-by-side' | 'split';
|
|
26
|
+
type TileflowComparisonMode = 'single' | 'blink' | 'overlay' | 'side-by-side' | 'split';
|
|
27
27
|
type TileflowComparisonOptions = {
|
|
28
28
|
basePath?: string;
|
|
29
29
|
initialMode?: TileflowComparisonMode;
|
|
30
30
|
left: TileflowComparisonSide;
|
|
31
|
-
right
|
|
31
|
+
right?: TileflowComparisonSide;
|
|
32
32
|
title?: string;
|
|
33
|
+
/** Optional local workflow adapter supplied by the CLI or an embedding server. */
|
|
34
|
+
workbench?: {
|
|
35
|
+
url: string;
|
|
36
|
+
handle: TileflowComparisonRequestHandler;
|
|
37
|
+
};
|
|
33
38
|
};
|
|
34
39
|
/**
|
|
35
|
-
* Compose two existing Tileflow dev handlers behind
|
|
40
|
+
* Compose one or two existing Tileflow dev handlers behind the same-origin Map DevTools.
|
|
36
41
|
*
|
|
37
42
|
* The side handlers retain ownership of styles, sprites, fonts, generations, live
|
|
38
43
|
* events, and last-known-good behavior. This router owns only the shell document and
|
|
@@ -46,6 +51,8 @@ type TileflowDevRequestHandlerOptions = {
|
|
|
46
51
|
config?: string;
|
|
47
52
|
cwd?: string;
|
|
48
53
|
inspection?: boolean;
|
|
54
|
+
/** Fit scene previews to their pane; capture retains the declared scene frame. */
|
|
55
|
+
fitViewport?: boolean;
|
|
49
56
|
map?: string;
|
|
50
57
|
onError?: (error: unknown) => void;
|
|
51
58
|
scene?: string;
|