@tileflow/dev 0.1.0-alpha.31 → 0.1.0-alpha.32
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 +59 -2
- package/THIRD_PARTY_NOTICES.md +225 -3
- package/dist/artifacts.d.ts +6 -2
- package/dist/artifacts.js +17 -2
- package/dist/{chunk-2QXKVQ6O.js → chunk-CM6ZTYDJ.js} +45 -5
- package/dist/{chunk-IR22ZTQW.js → chunk-F4PLTZS4.js} +447 -27
- package/dist/devtools-browser.js +17353 -0
- package/dist/devtools-observer.js +95 -0
- package/dist/fonts.d.ts +1 -1
- package/dist/icons-B84H8Ymu.d.ts +406 -0
- package/dist/icons.d.ts +3 -339
- package/dist/icons.js +3 -1
- package/dist/{preview-assets-MVUJJ5XR.js → preview-assets-KGUICQHG.js} +6 -0
- package/dist/preview.d.ts +1 -1
- package/dist/preview.js +206 -98
- package/dist/server.d.ts +11 -4
- package/dist/server.js +1594 -277
- package/docs/beacon-map-audits.md +102 -0
- package/docs/layers-inspection.md +87 -0
- package/docs/local-comparison-workbench-and-compiler-inspection.md +74 -171
- 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,67 @@ 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
|
+
|
|
143
199
|
// 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;
|
|
200
|
+
function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticPreview, fontFaces = [], fitViewport = false, controls) {
|
|
201
|
+
const styleUrl = preview ? `${basePath}/styles/${preview.mapName}/${preview.themeName}.json${controls ? `?${new URLSearchParams({ controls })}` : ""}` : void 0;
|
|
146
202
|
const mapOptions = preview ? previewMapOptions(preview) : void 0;
|
|
147
|
-
const viewportCss = preview?.viewport ? `
|
|
203
|
+
const viewportCss = preview?.viewport && !fitViewport ? `
|
|
148
204
|
html, body { min-height: 100%; margin: 0; }
|
|
149
205
|
body {
|
|
150
206
|
display: grid;
|
|
@@ -168,7 +224,6 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
168
224
|
${viewportCss}
|
|
169
225
|
body { font-family: ui-sans-serif, system-ui, sans-serif; }
|
|
170
226
|
#map {
|
|
171
|
-
--tileflow-globe-radius: 220px;
|
|
172
227
|
background-color: var(--tileflow-space, #2F5070);
|
|
173
228
|
background-image:
|
|
174
229
|
radial-gradient(circle at 18% 24%, rgba(225, 240, 250, 0.34) 0 1px, transparent 1.4px),
|
|
@@ -184,22 +239,8 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
184
239
|
radial-gradient(circle at 72% 62%, rgba(225, 240, 250, 0.22) 0 1px, transparent 1.3px),
|
|
185
240
|
radial-gradient(circle at 42% 78%, rgba(225, 240, 250, 0.18) 0 0.8px, transparent 1.2px);
|
|
186
241
|
}
|
|
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%);
|
|
242
|
+
#map.tileflow-globe .maplibregl-canvas {
|
|
243
|
+
filter: var(--tileflow-globe-rim, none);
|
|
203
244
|
}
|
|
204
245
|
#map.tileflow-globe > .maplibregl-canvas-container {
|
|
205
246
|
position: relative;
|
|
@@ -233,6 +274,7 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
233
274
|
padding: 12px;
|
|
234
275
|
white-space: pre-wrap;
|
|
235
276
|
}
|
|
277
|
+
html[data-devtools-embedded=true] .status { display:none !important; }
|
|
236
278
|
.maplibregl-ctrl-group .tileflow-3d-toggle,
|
|
237
279
|
.maplibregl-ctrl-group .tileflow-tree-toggle,
|
|
238
280
|
.maplibregl-ctrl-group .tileflow-theme-toggle {
|
|
@@ -253,6 +295,12 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
253
295
|
.maplibregl-ctrl-group .tileflow-theme-toggle:hover {
|
|
254
296
|
background-color: #F1F3F4;
|
|
255
297
|
}
|
|
298
|
+
.maplibregl-ctrl-group { background:#faf6eb; border:1px solid #e0d5c4; border-radius:3px; box-shadow:0 3px 12px #30251f18; }
|
|
299
|
+
.maplibregl-ctrl-group button { color:#30251f; }
|
|
300
|
+
.maplibregl-ctrl-group button + button { border-top-color:#e0d5c4; }
|
|
301
|
+
.maplibregl-ctrl-group .tileflow-3d-toggle,.maplibregl-ctrl-group .tileflow-tree-toggle,.maplibregl-ctrl-group .tileflow-theme-toggle { color:#30251f; font-weight:500; }
|
|
302
|
+
.maplibregl-ctrl-group .tileflow-3d-toggle[aria-pressed="true"],.maplibregl-ctrl-group .tileflow-tree-toggle[aria-pressed="true"] { background:#f0dfe0; color:#8c2e3f; }
|
|
303
|
+
.maplibregl-ctrl-group .tileflow-3d-toggle:hover,.maplibregl-ctrl-group .tileflow-tree-toggle:hover,.maplibregl-ctrl-group .tileflow-theme-toggle:hover { background:#eee4d3; }
|
|
256
304
|
</style>
|
|
257
305
|
</head>
|
|
258
306
|
<body>
|
|
@@ -262,19 +310,25 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
262
310
|
<script type="importmap">{"imports":{"fflate":"${basePath}/__runtime/fflate.js","three":"${basePath}/__runtime/three.module.js"}}</script>
|
|
263
311
|
<script type="module">
|
|
264
312
|
import * as maplibregl from "${basePath}/__runtime/maplibre-gl.mjs";
|
|
313
|
+
import {createPreviewObserver} from "${basePath}/__runtime/devtools-observer.js";
|
|
265
314
|
import {loadTileflowStyleFonts} from "${basePath}/__runtime/tileflow-browser.js";
|
|
266
315
|
import {registerTileflowContourProtocol} from "${basePath}/__runtime/tileflow-browser.js";
|
|
267
316
|
import {registerTileflowPmtilesProtocol} from "${basePath}/__runtime/tileflow-browser.js";
|
|
268
|
-
import {createTileflowBuildings3D, createTileflowStreetLights,
|
|
317
|
+
import {createTileflowBuildings3D, createTileflowStreetLights, createTileflowVegetation} from "${basePath}/__runtime/tileflow-browser.js";
|
|
269
318
|
import {readTileflowSunPreset, resolveTileflowSun, tileflowDefaultSunPreset} from "${basePath}/__runtime/tileflow-browser.js";
|
|
270
319
|
|
|
271
320
|
maplibregl.setWorkerUrl("${basePath}/__runtime/maplibre-gl-worker.mjs");
|
|
272
321
|
|
|
322
|
+
const devtoolsObserver = window.parent !== window ? createPreviewObserver() : undefined;
|
|
323
|
+
globalThis.__tileflowDevtoolsObserver = devtoolsObserver;
|
|
324
|
+
if (window.parent !== window) document.documentElement.dataset.devtoolsEmbedded = "true";
|
|
325
|
+
|
|
273
326
|
const initialStatus = ${JSON.stringify(initialStatus)};
|
|
274
327
|
const normalizeTileflowLandmarkManifest = ${normalizeTileflowLandmarkManifest.toString()};
|
|
275
328
|
const readBoundedTileflowResponse = ${readBoundedTileflowResponse.toString()};
|
|
276
329
|
const readBoundedTileflowJsonResponse = ${readBoundedTileflowJsonResponse.toString()};
|
|
277
330
|
const initialGeneration = initialStatus.generation;
|
|
331
|
+
document.documentElement.dataset.tileflowArtifactGeneration = String(initialStatus.lastGoodGeneration ?? initialGeneration);
|
|
278
332
|
const badge = document.getElementById("badge");
|
|
279
333
|
const status = document.getElementById("status");
|
|
280
334
|
const previewLabel = ${JSON.stringify(preview?.label)};
|
|
@@ -319,7 +373,7 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
319
373
|
return fallback;
|
|
320
374
|
}
|
|
321
375
|
let threeDimensionalEnabled = readToggleFromUrl("buildings3d", false);
|
|
322
|
-
let treesEnabled = readToggleFromUrl("trees3d",
|
|
376
|
+
let treesEnabled = readToggleFromUrl("trees3d", true);
|
|
323
377
|
const landmarkState = {
|
|
324
378
|
active: [],
|
|
325
379
|
cacheLimit: undefined,
|
|
@@ -940,14 +994,7 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
940
994
|
return;
|
|
941
995
|
}
|
|
942
996
|
|
|
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
997
|
container.classList.add("tileflow-globe");
|
|
950
|
-
container.style.setProperty("--tileflow-globe-radius", radius.toFixed(2) + "px");
|
|
951
998
|
applyAtmosphereBackdrop(map, container);
|
|
952
999
|
}
|
|
953
1000
|
|
|
@@ -961,12 +1008,16 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
961
1008
|
const value = Number.parseInt(match[1], 16);
|
|
962
1009
|
return "rgba(" + (value >> 16) + ", " + ((value >> 8) & 255) + ", " + (value & 255) + ", " + alpha + ")";
|
|
963
1010
|
};
|
|
1011
|
+
// Use the rendered canvas alpha, as the shared browser atmosphere does.
|
|
1012
|
+
// A zoom-only circle cannot track the globe's latitude, viewport or pitch.
|
|
1013
|
+
const opacity = Math.max(0, Math.min(1, (6 - map.getZoom()) / 3));
|
|
1014
|
+
const core = rgba(atmosphere?.horizonColor ?? "#F8FCFF", opacity * 0.65);
|
|
1015
|
+
const glow = rgba(atmosphere?.horizonColor ?? "#B0DCF6", opacity * 0.4);
|
|
964
1016
|
const properties = {
|
|
965
1017
|
"--tileflow-space": rgba(atmosphere?.spaceColor, 1),
|
|
966
|
-
"--tileflow-rim
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
"--tileflow-rim-haze": rgba(atmosphere?.skyColor, 0.35),
|
|
1018
|
+
"--tileflow-globe-rim": opacity > 0
|
|
1019
|
+
? "drop-shadow(0 0 2px " + core + ") drop-shadow(0 0 12px " + glow + ")"
|
|
1020
|
+
: "none",
|
|
970
1021
|
};
|
|
971
1022
|
for (const [name, value] of Object.entries(properties)) {
|
|
972
1023
|
if (value) container.style.setProperty(name, value);
|
|
@@ -1921,6 +1972,7 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
1921
1972
|
(layer) =>
|
|
1922
1973
|
(layer.id.startsWith("tileflow-label-") &&
|
|
1923
1974
|
!layer.id.startsWith("tileflow-label-road-")) ||
|
|
1975
|
+
layer.id === "tileflow-poi" ||
|
|
1924
1976
|
layer.id.startsWith("tileflow-poi-")
|
|
1925
1977
|
)?.id;
|
|
1926
1978
|
const treeLayer = map.getLayer("tileflow-vegetation-trees")
|
|
@@ -1930,11 +1982,25 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
1930
1982
|
}
|
|
1931
1983
|
|
|
1932
1984
|
// 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
|
-
//
|
|
1985
|
+
// buildings, trees and street lights look, and the controls only show and hide them. The
|
|
1986
|
+
// vegetation control hides all trees when off. The preview shows one theme at a
|
|
1935
1987
|
// time, so the style on screen holds the sun and the design.
|
|
1936
|
-
const coreScene = {buildings: undefined, trees: undefined, lights: undefined, buildingsShown: false,
|
|
1988
|
+
const coreScene = {buildings: undefined, trees: undefined, lights: undefined, buildingsShown: false, animation: "0"};
|
|
1989
|
+
const runtimeLayerVisibility = {};
|
|
1937
1990
|
globalThis.__tileflowPreviewScene = coreScene;
|
|
1991
|
+
globalThis.__tileflowRuntimeVisibility = (id, visible) => {
|
|
1992
|
+
if (!['tileflow-buildings-3d','tileflow-trees-3d','tileflow-street-lights'].includes(id)) return undefined;
|
|
1993
|
+
if (typeof visible === 'boolean') { runtimeLayerVisibility[id]=visible; mountCoreScene(map); map.triggerRepaint(); }
|
|
1994
|
+
return runtimeLayerVisibility[id] !== false;
|
|
1995
|
+
};
|
|
1996
|
+
// Explicit observation capability: controllers stay private to the preview and expose
|
|
1997
|
+
// only their public state/statistics to the parent inspector.
|
|
1998
|
+
globalThis.__tileflowInspectModels = () => [...(coreScene.buildings?.inspectModels?.() || []), ...(coreScene.trees?.inspectModels?.() || [])];
|
|
1999
|
+
globalThis.__tileflowDescribeRuntimeLayers = () => ({
|
|
2000
|
+
...(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}} : {}),
|
|
2001
|
+
...(coreScene.trees ? {"tileflow-trees-3d": {controller:"Vegetation", state:{visible:runtimeLayerVisibility["tileflow-trees-3d"] !== false, representation:coreScene.trees.representation}, stats:coreScene.trees.stats}} : {}),
|
|
2002
|
+
...(coreScene.lights ? {"tileflow-street-lights": {controller:"Street lights", state:{visible:runtimeLayerVisibility["tileflow-street-lights"] !== false, mounted:true}, stats:coreScene.lights.stats}} : {})
|
|
2003
|
+
});
|
|
1938
2004
|
function coreSceneThemes(map) {
|
|
1939
2005
|
return {
|
|
1940
2006
|
getSun() {
|
|
@@ -1946,32 +2012,52 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
1946
2012
|
}
|
|
1947
2013
|
};
|
|
1948
2014
|
}
|
|
2015
|
+
let mountingCoreScene = false;
|
|
1949
2016
|
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
|
-
|
|
2017
|
+
if (mountingCoreScene) return;
|
|
2018
|
+
mountingCoreScene = true;
|
|
2019
|
+
try {
|
|
2020
|
+
const metadata = map.getStyle()?.metadata ?? {};
|
|
2021
|
+
const controller = coreSceneThemes(map);
|
|
2022
|
+
if (!coreScene.buildings && metadata["tileflow:buildings"]) {
|
|
2023
|
+
coreScene.buildings = createTileflowBuildings3D({map, controller});
|
|
2024
|
+
}
|
|
2025
|
+
if (!coreScene.trees) {
|
|
2026
|
+
if (!previewSearchParameters.has("trees3d")) {
|
|
2027
|
+
treesEnabled = metadata["tileflow:vegetation-display"]?.visible ?? true;
|
|
2028
|
+
}
|
|
2029
|
+
coreScene.trees = createTileflowVegetation({map, controller, visible: treesEnabled,
|
|
2030
|
+
viewMode: threeDimensionalEnabled ? "3d" : "2d", transformRequest: previewTransformRequest});
|
|
2031
|
+
}
|
|
2032
|
+
coreScene.trees.setVisible(treesEnabled && runtimeLayerVisibility["tileflow-trees-3d"] !== false);
|
|
2033
|
+
coreScene.trees.setViewMode(threeDimensionalEnabled ? "3d" : "2d");
|
|
2034
|
+
// Street lights have no capture fallback, so they shine only with the 3D control on.
|
|
2035
|
+
if (!coreScene.lights && threeDimensionalEnabled && metadata["tileflow:street-lights"]) {
|
|
2036
|
+
coreScene.lights = createTileflowStreetLights({map, controller});
|
|
2037
|
+
} else if (coreScene.lights && !threeDimensionalEnabled) {
|
|
2038
|
+
coreScene.lights.dispose();
|
|
2039
|
+
coreScene.lights = undefined;
|
|
2040
|
+
}
|
|
2041
|
+
const lightLayer = map.getLayer("tileflow-street-lights");
|
|
2042
|
+
const lightRenderer = lightLayer?.implementation || lightLayer;
|
|
2043
|
+
if (lightRenderer?.render && !lightRenderer.__previewVisibility) {
|
|
2044
|
+
const render = lightRenderer.render;
|
|
2045
|
+
lightRenderer.render = function(...args) {
|
|
2046
|
+
if (runtimeLayerVisibility["tileflow-street-lights"] !== false) return render.apply(this,args);
|
|
2047
|
+
};
|
|
2048
|
+
lightRenderer.__previewVisibility = true;
|
|
2049
|
+
}
|
|
2050
|
+
const showBuildings = threeDimensionalEnabled && runtimeLayerVisibility["tileflow-buildings-3d"] !== false;
|
|
2051
|
+
const instant = {transition: {duration: 0}};
|
|
2052
|
+
const animation = previewSearchParameters.get('animation') || '0';
|
|
2053
|
+
const duration = animation === 'fade' ? 800 : Math.min(3000, Math.max(0, Number(animation) || 0));
|
|
2054
|
+
// Only a change of the controls moves the layers, so a style event does not restart them.
|
|
2055
|
+
if (coreScene.buildings && (coreScene.buildingsShown !== showBuildings || coreScene.animation !== animation)) {
|
|
2056
|
+
coreScene.buildingsShown = showBuildings;
|
|
2057
|
+
coreScene.animation = animation;
|
|
2058
|
+
void (showBuildings ? coreScene.buildings.show({transition: {duration, mode: animation === "fade" ? "fade" : "grow"}}) : coreScene.buildings.hide(instant));
|
|
2059
|
+
}
|
|
2060
|
+
} finally {mountingCoreScene = false;}
|
|
1975
2061
|
}
|
|
1976
2062
|
|
|
1977
2063
|
class ThreeDimensionalControl {
|
|
@@ -2076,18 +2162,11 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
2076
2162
|
}
|
|
2077
2163
|
|
|
2078
2164
|
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
|
-
}
|
|
2165
|
+
if (this.map.isStyleLoaded()) mountCoreScene(this.map);
|
|
2166
|
+
this.enabled = treesEnabled;
|
|
2088
2167
|
this.button.textContent = this.enabled ? "TREES ON" : "TREES OFF";
|
|
2089
2168
|
this.button.setAttribute("aria-pressed", String(this.enabled));
|
|
2090
|
-
this.button.title = this.enabled ? "
|
|
2169
|
+
this.button.title = this.enabled ? "Hide vegetation" : "Show vegetation";
|
|
2091
2170
|
}
|
|
2092
2171
|
|
|
2093
2172
|
onRemove() {
|
|
@@ -2183,24 +2262,21 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
2183
2262
|
...visibleLayerGroups,
|
|
2184
2263
|
].join(",");
|
|
2185
2264
|
}
|
|
2265
|
+
const vegetationLayers = map.getStyle()?.metadata?.["tileflow:vegetation-display"]?.flatLayers;
|
|
2186
2266
|
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
2267
|
if (layer.type === "symbol" && layer.layout?.["text-field"] !== undefined) {
|
|
2194
|
-
|
|
2268
|
+
const hideLabels = !visibleLayerGroups.has("labels");
|
|
2269
|
+
if (hideLabels && !defaultLayerTextField.has(layer.id)) {
|
|
2195
2270
|
defaultLayerTextField.set(
|
|
2196
2271
|
layer.id,
|
|
2197
2272
|
map.getLayoutProperty(layer.id, "text-field")
|
|
2198
2273
|
);
|
|
2199
2274
|
}
|
|
2200
|
-
const
|
|
2201
|
-
|
|
2202
|
-
|
|
2275
|
+
const restoreText = !hideLabels && defaultLayerTextField.has(layer.id);
|
|
2276
|
+
const textField = hideLabels ? "" : defaultLayerTextField.get(layer.id);
|
|
2277
|
+
if (restoreText) defaultLayerTextField.delete(layer.id);
|
|
2203
2278
|
if (
|
|
2279
|
+
(hideLabels || restoreText) &&
|
|
2204
2280
|
!previewLayoutValuesEqual(
|
|
2205
2281
|
map.getLayoutProperty(layer.id, "text-field"),
|
|
2206
2282
|
textField
|
|
@@ -2209,21 +2285,29 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
2209
2285
|
map.setLayoutProperty(layer.id, "text-field", textField);
|
|
2210
2286
|
}
|
|
2211
2287
|
}
|
|
2288
|
+
// The shared vegetation controller owns every flat representation.
|
|
2289
|
+
if (vegetationLayers?.some((entry) => entry.id === layer.id.split("::")[0])) continue;
|
|
2212
2290
|
const groups = previewLayerGroups(layer);
|
|
2291
|
+
const hideLayer = groups.some((group) => !visibleLayerGroups.has(group));
|
|
2292
|
+
// Group controls only own the properties they mask. Ordinary style events must
|
|
2293
|
+
// preserve temporary DevTools edits, including visibility and label text.
|
|
2294
|
+
if (hideLayer && !defaultLayerVisibility.has(layer.id)) {
|
|
2295
|
+
defaultLayerVisibility.set(layer.id, map.getLayoutProperty(layer.id, "visibility") || "visible");
|
|
2296
|
+
}
|
|
2297
|
+
if (!hideLayer && !defaultLayerVisibility.has(layer.id)) continue;
|
|
2213
2298
|
const threeDimensionalToggle = layer.metadata?.["tileflow:3d-toggle"];
|
|
2214
2299
|
const authoredVisibility = defaultLayerVisibility.get(layer.id);
|
|
2300
|
+
if (!hideLayer) defaultLayerVisibility.delete(layer.id);
|
|
2215
2301
|
const styleMetadata = map.getStyle()?.metadata ?? {};
|
|
2216
2302
|
// Core's 3D buildings and trees stand in for compiled extrusions and flat trees.
|
|
2217
|
-
const flatTrees = styleMetadata["tileflow:vegetation"]?.flatLayers ?? [];
|
|
2218
2303
|
const replacedByCore =
|
|
2219
|
-
|
|
2220
|
-
(treesEnabled && flatTrees.includes(layer.id.split("::")[0]));
|
|
2304
|
+
threeDimensionalToggle === "building" && styleMetadata["tileflow:buildings"];
|
|
2221
2305
|
const toggleVisibility = replacedByCore
|
|
2222
2306
|
? "none"
|
|
2223
2307
|
: threeDimensionalToggle === "building" || threeDimensionalToggle === "landmark"
|
|
2224
2308
|
? (threeDimensionalEnabled ? "visible" : "none")
|
|
2225
2309
|
: authoredVisibility;
|
|
2226
|
-
const visibility =
|
|
2310
|
+
const visibility = hideLayer
|
|
2227
2311
|
? "none"
|
|
2228
2312
|
: toggleVisibility;
|
|
2229
2313
|
if (map.getLayoutProperty(layer.id, "visibility") !== visibility) {
|
|
@@ -2238,6 +2322,16 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
2238
2322
|
map.fire("tileflow:visible-layer-groups", {groups: [...visibleLayerGroups]});
|
|
2239
2323
|
}
|
|
2240
2324
|
|
|
2325
|
+
function previewTransformRequest(url) {
|
|
2326
|
+
try {
|
|
2327
|
+
return new URL(url, location.href).hostname === "dev-tiles.tileflow.dev"
|
|
2328
|
+
? {credentials: "include", url}
|
|
2329
|
+
: {url};
|
|
2330
|
+
} catch {
|
|
2331
|
+
return {url};
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2241
2335
|
if (styleUrl) {
|
|
2242
2336
|
// Dense vector tiles finish in bursts with multiple workers, causing several
|
|
2243
2337
|
// bucket uploads in one frame. One worker keeps navigation smooth; benchmarks
|
|
@@ -2248,23 +2342,16 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
2248
2342
|
registerTileflowContourProtocol({addProtocol: maplibregl.addProtocol});
|
|
2249
2343
|
registerTileflowPmtilesProtocol({addProtocol: maplibregl.addProtocol});
|
|
2250
2344
|
const map = new maplibregl.Map({
|
|
2251
|
-
collectResourceTiming: mapBenchmarkEnabled,
|
|
2345
|
+
collectResourceTiming: mapBenchmarkEnabled || Boolean(devtoolsObserver),
|
|
2252
2346
|
container: "map",
|
|
2253
2347
|
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
|
-
},
|
|
2348
|
+
transformRequest: previewTransformRequest,
|
|
2263
2349
|
...resolveInitialMapOptions(previewMapOptions)
|
|
2264
2350
|
});
|
|
2265
2351
|
globalThis.__tileflowPreviewMap = map;
|
|
2352
|
+
devtoolsObserver?.attach(map);
|
|
2266
2353
|
installMapBenchmark(map);
|
|
2267
|
-
map.addControl(new maplibregl.NavigationControl(), "top-right");
|
|
2354
|
+
if (window.parent === window) map.addControl(new maplibregl.NavigationControl(), "top-right");
|
|
2268
2355
|
let threeDimensionalControl;
|
|
2269
2356
|
let treeControl;
|
|
2270
2357
|
if (isSemanticPreview) {
|
|
@@ -2272,8 +2359,16 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
2272
2359
|
treeControl = new TreeControl();
|
|
2273
2360
|
map.addControl(threeDimensionalControl, "top-right");
|
|
2274
2361
|
map.addControl(treeControl, "top-right");
|
|
2362
|
+
// Embedded Map DevTools owns the common Controls sidebar. Keep the controls mounted
|
|
2363
|
+
// for their renderer lifecycle and message bridge, but remove duplicate buttons.
|
|
2364
|
+
if (window.parent !== window) {
|
|
2365
|
+
threeDimensionalControl.container.hidden = true;
|
|
2366
|
+
treeControl.container.hidden = true;
|
|
2367
|
+
}
|
|
2275
2368
|
}
|
|
2276
|
-
|
|
2369
|
+
// Map DevTools has a shared theme selector in its Controls panel; keep the map-level
|
|
2370
|
+
// cycling button for standalone previews only.
|
|
2371
|
+
if (previewThemes.length > 1 && window.parent === window) map.addControl(new ThemeControl(), "top-right");
|
|
2277
2372
|
let ensuringThreeDimensionalLayers;
|
|
2278
2373
|
const ensureThreeDimensionalLayers = () => {
|
|
2279
2374
|
if (ensuringThreeDimensionalLayers) return ensuringThreeDimensionalLayers;
|
|
@@ -2360,13 +2455,16 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
2360
2455
|
map.on("tileflow:3d-toggle", ensureThreeDimensionalLayers);
|
|
2361
2456
|
map.on("tileflow:trees-toggle", ensureThreeDimensionalLayers);
|
|
2362
2457
|
map.on("load", () => {
|
|
2458
|
+
mountCoreScene(map);
|
|
2459
|
+
treeControl?.update();
|
|
2363
2460
|
writeCameraToUrl(map);
|
|
2364
2461
|
updateGlobeBackdrop(map);
|
|
2365
2462
|
ensureThreeDimensionalLayers();
|
|
2366
|
-
mountCoreScene(map);
|
|
2367
2463
|
});
|
|
2368
2464
|
map.on("move", () => updateGlobeBackdrop(map));
|
|
2465
|
+
map.on("style.load", () => updateGlobeBackdrop(map));
|
|
2369
2466
|
map.on("moveend", () => writeCameraToUrl(map));
|
|
2467
|
+
${previewSceneClient}
|
|
2370
2468
|
addEventListener("message", (event) => {
|
|
2371
2469
|
if (
|
|
2372
2470
|
window.parent === window ||
|
|
@@ -2437,6 +2535,15 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
2437
2535
|
}
|
|
2438
2536
|
|
|
2439
2537
|
applyStatus(initialStatus);
|
|
2538
|
+
// Embedded previews share their host's stream. Two streams per tab can exhaust Chrome's
|
|
2539
|
+
// six HTTP/1 connections and leave even local module/font requests waiting indefinitely.
|
|
2540
|
+
let unsubscribePreviewStatus;
|
|
2541
|
+
try {
|
|
2542
|
+
if (window.parent !== window) unsubscribePreviewStatus = window.parent.__tileflowDevtoolsHost?.subscribePreviewStatus?.(window, applyStatus);
|
|
2543
|
+
} catch { /* Other embedders keep the standalone stream. */ }
|
|
2544
|
+
if (unsubscribePreviewStatus) {
|
|
2545
|
+
addEventListener("beforeunload", unsubscribePreviewStatus);
|
|
2546
|
+
} else {
|
|
2440
2547
|
const events = new EventSource(${JSON.stringify(`${basePath}/__events`)});
|
|
2441
2548
|
// Generations are monotonic only within one dev-server process. A source-code watcher can
|
|
2442
2549
|
// restart that process at generation 1, so reconnecting after a live connection was lost
|
|
@@ -2457,6 +2564,7 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
2457
2564
|
for (const eventName of ["building", "ready", "invalid"]) {
|
|
2458
2565
|
events.addEventListener(eventName, (event) => applyStatus(JSON.parse(event.data)));
|
|
2459
2566
|
}
|
|
2567
|
+
}
|
|
2460
2568
|
</script>
|
|
2461
2569
|
</body>
|
|
2462
2570
|
</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-B84H8Ymu.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;
|