@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/server.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
parseTileflowLineBackgroundGlyphPath,
|
|
6
6
|
tileflowArtifactSessionSchemaVersion,
|
|
7
7
|
tileflowLineBackgroundGlyphRoute
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-WJOF6USY.js";
|
|
9
9
|
import {
|
|
10
10
|
getTileflowAssetBasePath,
|
|
11
11
|
getTileflowAssetFileName,
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
normalizeTileflowBasePath,
|
|
14
14
|
resolveTileflowArtifactPublicUrls
|
|
15
15
|
} from "./chunk-UTRO4IBQ.js";
|
|
16
|
-
import "./chunk-
|
|
16
|
+
import "./chunk-QYU4OW5A.js";
|
|
17
17
|
import "./chunk-7C4F6GAX.js";
|
|
18
18
|
import {
|
|
19
19
|
TileflowStyleValidationError
|
|
@@ -34,12 +34,1492 @@ import {
|
|
|
34
34
|
tileflowThemeNameSchema
|
|
35
35
|
} from "@tileflow/core";
|
|
36
36
|
|
|
37
|
+
// src/preview-controls.ts
|
|
38
|
+
import { parseResolvedTileflowMap, tileflowStyleFontFacesMetadataKey } from "@tileflow/core";
|
|
39
|
+
import { createStyleFromCatalogWithInspection } from "@tileflow/core/build";
|
|
40
|
+
var moduleNames = [
|
|
41
|
+
"land",
|
|
42
|
+
"water",
|
|
43
|
+
"nautical",
|
|
44
|
+
"roads",
|
|
45
|
+
"boundaries",
|
|
46
|
+
"labels",
|
|
47
|
+
"poi",
|
|
48
|
+
"aeroways",
|
|
49
|
+
"transit",
|
|
50
|
+
"addresses",
|
|
51
|
+
"landforms",
|
|
52
|
+
"buildings",
|
|
53
|
+
"vegetation"
|
|
54
|
+
];
|
|
55
|
+
var fieldRules = {
|
|
56
|
+
"buildings.mode": ["flat", "3d"],
|
|
57
|
+
"buildings.threeDimensional.lighting": ["style", "sun"],
|
|
58
|
+
"buildings.threeDimensional.shadows": "boolean",
|
|
59
|
+
"buildings.threeDimensional.occlusion": "boolean",
|
|
60
|
+
"buildings.threeDimensional.windows": "boolean",
|
|
61
|
+
"buildings.threeDimensional.edges": [0, 1],
|
|
62
|
+
"buildings.threeDimensional.colorVariation": [0, 1],
|
|
63
|
+
"buildings.threeDimensional.nightLight": [0, 2],
|
|
64
|
+
"vegetation.mode": ["3d", "flat", "auto"],
|
|
65
|
+
"vegetation.fallback": ["flat", "none"],
|
|
66
|
+
"vegetation.visible": "boolean",
|
|
67
|
+
"vegetation.threeDimensional.finish": ["faceted", "smooth", "toon"],
|
|
68
|
+
"vegetation.threeDimensional.edges": [0, 1],
|
|
69
|
+
"vegetation.threeDimensional.heightScale": [0, 10],
|
|
70
|
+
"vegetation.threeDimensional.crownScale": [0, 10]
|
|
71
|
+
};
|
|
72
|
+
function record(value) {
|
|
73
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
74
|
+
}
|
|
75
|
+
function parsePreviewControls(text) {
|
|
76
|
+
if (text.length > 8192) throw new Error("Map controls exceed 8 KiB.");
|
|
77
|
+
const input = JSON.parse(text);
|
|
78
|
+
if (!record(input)) throw new Error("Map controls must be an object.");
|
|
79
|
+
for (const [module, patch] of Object.entries(input)) {
|
|
80
|
+
if (!moduleNames.includes(module) || !record(patch))
|
|
81
|
+
throw new Error("Unknown map control module: " + module);
|
|
82
|
+
const validate = (path, value) => {
|
|
83
|
+
const rule = path === module + ".enabled" ? "boolean" : fieldRules[path];
|
|
84
|
+
const valid = rule === "boolean" ? typeof value === "boolean" : rule && (typeof rule[0] === "number" ? typeof value === "number" && Number.isFinite(value) && value >= rule[0] && value <= rule[1] : typeof value === "string" && rule.includes(value));
|
|
85
|
+
if (!valid) throw new Error("Invalid map control: " + path);
|
|
86
|
+
};
|
|
87
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
88
|
+
if (key === "threeDimensional" && record(value) && ["buildings", "vegetation"].includes(module)) {
|
|
89
|
+
for (const [nested, entry] of Object.entries(value))
|
|
90
|
+
validate(module + "." + key + "." + nested, entry);
|
|
91
|
+
} else validate(module + "." + key, value);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return input;
|
|
95
|
+
}
|
|
96
|
+
function describePreviewControls(artifacts, map, theme) {
|
|
97
|
+
const definition = artifacts.project.maps[map];
|
|
98
|
+
const style = artifacts.styles[map]?.[theme];
|
|
99
|
+
if (!definition || !style) throw new Error("Unknown map theme.");
|
|
100
|
+
const modules = definition.modules ?? {};
|
|
101
|
+
const metadata = style.metadata ?? {};
|
|
102
|
+
return {
|
|
103
|
+
map,
|
|
104
|
+
theme,
|
|
105
|
+
modules: moduleNames.map((id) => {
|
|
106
|
+
const config = modules[id] ?? { type: id };
|
|
107
|
+
const fields = [];
|
|
108
|
+
const record2 = config;
|
|
109
|
+
const add = (path, label, value, choices, min, max, step, inherit = false) => fields.push({
|
|
110
|
+
path,
|
|
111
|
+
label,
|
|
112
|
+
value,
|
|
113
|
+
...choices ? { choices } : {},
|
|
114
|
+
...inherit ? { inherit: true } : {},
|
|
115
|
+
...min !== void 0 ? { min, max, step } : {}
|
|
116
|
+
});
|
|
117
|
+
if (id === "buildings") {
|
|
118
|
+
const design = metadata["tileflow:buildings"];
|
|
119
|
+
const authored = record2.threeDimensional;
|
|
120
|
+
const value = (key, fallback) => {
|
|
121
|
+
const resolved = design?.[key] ?? authored?.[key];
|
|
122
|
+
return typeof resolved === typeof fallback ? resolved : fallback;
|
|
123
|
+
};
|
|
124
|
+
add("threeDimensional.lighting", "Building light", value("lighting", "style"), [
|
|
125
|
+
"style",
|
|
126
|
+
"sun"
|
|
127
|
+
]);
|
|
128
|
+
for (const [key, label] of [
|
|
129
|
+
["shadows", "Cast shadows"],
|
|
130
|
+
["occlusion", "Contact shadows"],
|
|
131
|
+
["windows", "Night windows"]
|
|
132
|
+
])
|
|
133
|
+
add("threeDimensional." + key, label, value(key, false));
|
|
134
|
+
for (const [key, label] of [
|
|
135
|
+
["edges", "Edge definition"],
|
|
136
|
+
["colorVariation", "Color variation"],
|
|
137
|
+
["nightLight", "Night glow"]
|
|
138
|
+
])
|
|
139
|
+
add(
|
|
140
|
+
"threeDimensional." + key,
|
|
141
|
+
label,
|
|
142
|
+
value(key, 0),
|
|
143
|
+
void 0,
|
|
144
|
+
0,
|
|
145
|
+
key === "nightLight" ? 2 : 1,
|
|
146
|
+
0.05
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
if (id === "vegetation") {
|
|
150
|
+
add(
|
|
151
|
+
"mode",
|
|
152
|
+
"Tree representation",
|
|
153
|
+
String(record2.mode ?? "3d"),
|
|
154
|
+
["3d", "auto", "flat"],
|
|
155
|
+
void 0,
|
|
156
|
+
void 0,
|
|
157
|
+
void 0,
|
|
158
|
+
true
|
|
159
|
+
);
|
|
160
|
+
add("fallback", "Without 3D support", String(record2.fallback ?? "flat"), ["flat", "none"]);
|
|
161
|
+
add("visible", "Visible by default", record2.visible !== false);
|
|
162
|
+
const authored = record2.threeDimensional;
|
|
163
|
+
const design = metadata["tileflow:vegetation"];
|
|
164
|
+
add("threeDimensional.finish", "Tree finish", String(authored?.finish ?? "faceted"), [
|
|
165
|
+
"faceted",
|
|
166
|
+
"smooth",
|
|
167
|
+
"toon"
|
|
168
|
+
]);
|
|
169
|
+
for (const [key, label] of [
|
|
170
|
+
["heightScale", "Tree height"],
|
|
171
|
+
["crownScale", "Crown size"]
|
|
172
|
+
]) {
|
|
173
|
+
const value = design?.[key] ?? authored?.[key];
|
|
174
|
+
add(
|
|
175
|
+
"threeDimensional." + key,
|
|
176
|
+
label,
|
|
177
|
+
typeof value === "number" ? value : 1,
|
|
178
|
+
void 0,
|
|
179
|
+
0,
|
|
180
|
+
10,
|
|
181
|
+
0.1
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return { id, enabled: record2.enabled !== false, fields };
|
|
186
|
+
})
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
function compilePreviewControls(artifacts, map, theme, text, apiBaseUrl) {
|
|
190
|
+
if (artifacts.nativeBuild) throw new Error("Map controls require a web preview.");
|
|
191
|
+
const overrides = parsePreviewControls(text);
|
|
192
|
+
const definition = artifacts.project.maps[map];
|
|
193
|
+
const base = artifacts.styles[map]?.[theme];
|
|
194
|
+
if (!definition || !base) throw new Error("Unknown map theme.");
|
|
195
|
+
const modules = { ...definition.modules };
|
|
196
|
+
for (const [id, patch] of Object.entries(overrides)) {
|
|
197
|
+
const key = id;
|
|
198
|
+
const original = modules[key] ?? { type: id };
|
|
199
|
+
const merged = { ...original, ...patch };
|
|
200
|
+
if ("threeDimensional" in patch && patch.threeDimensional)
|
|
201
|
+
Object.assign(merged, {
|
|
202
|
+
threeDimensional: {
|
|
203
|
+
..."threeDimensional" in original ? original.threeDimensional : {},
|
|
204
|
+
...patch.threeDimensional
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
Object.assign(modules, { [id]: merged });
|
|
208
|
+
}
|
|
209
|
+
const resolved = parseResolvedTileflowMap({ ...definition, modules });
|
|
210
|
+
const result = createStyleFromCatalogWithInspection(
|
|
211
|
+
{ ...artifacts.project, maps: { ...artifacts.project.maps, [map]: resolved } },
|
|
212
|
+
map,
|
|
213
|
+
{
|
|
214
|
+
theme,
|
|
215
|
+
apiBaseUrl,
|
|
216
|
+
preparedAssets: artifacts.mapAssets?.[map]
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
result.style.sources = Object.fromEntries(
|
|
220
|
+
Object.entries(result.style.sources).map(([id, source]) => [id, base.sources[id] ?? source])
|
|
221
|
+
);
|
|
222
|
+
if (base.glyphs) result.style.glyphs = base.glyphs;
|
|
223
|
+
if (base.sprite) result.style.sprite = base.sprite;
|
|
224
|
+
if (base.metadata?.[tileflowStyleFontFacesMetadataKey])
|
|
225
|
+
result.style.metadata = {
|
|
226
|
+
...result.style.metadata,
|
|
227
|
+
[tileflowStyleFontFacesMetadataKey]: base.metadata[tileflowStyleFontFacesMetadataKey]
|
|
228
|
+
};
|
|
229
|
+
return result;
|
|
230
|
+
}
|
|
231
|
+
|
|
37
232
|
// src/comparison.ts
|
|
38
233
|
import { randomBytes } from "crypto";
|
|
234
|
+
import { readFile } from "fs/promises";
|
|
235
|
+
|
|
236
|
+
// src/devtools/card-styles.ts
|
|
237
|
+
var cardStyles = `
|
|
238
|
+
.devtools-ready .dt-shell {
|
|
239
|
+
--dt-page:color-mix(in srgb,var(--surface) 80%,var(--border));
|
|
240
|
+
--dt-card-heading:color-mix(in srgb,var(--paper) 55%,var(--surface));
|
|
241
|
+
--dt-content-gap:12px;
|
|
242
|
+
}
|
|
243
|
+
.devtools-ready .dt-shell :is(.dt-card-page,.dt-layer-content,.dt-tile-content,.dt-diagnostics-list) {
|
|
244
|
+
padding:14px; background:var(--dt-page);
|
|
245
|
+
}
|
|
246
|
+
.devtools-ready .dt-shell :is(.dt-card,.dt-layer-evidence,.dt-layer-neighbors,.dt-layer-section,.dt-layer-origin,.dt-layer-samples,.dt-tile-content > .dt-kv,.dt-tile-feature,.dt-diagnostic,.dt-diagnostics-empty) {
|
|
247
|
+
min-width:0; margin:0 0 var(--dt-content-gap); border:1px solid var(--border); border-radius:10px; background:var(--paper);
|
|
248
|
+
}
|
|
249
|
+
.devtools-ready .dt-shell .dt-card { padding:0; overflow:hidden; }
|
|
250
|
+
.devtools-ready .dt-shell :is(.dt-card-page,.dt-layer-content,.dt-tile-content,.dt-diagnostics-list) > :last-child { margin-bottom:0; }
|
|
251
|
+
.devtools-ready .dt-shell :is(.dt-card > .dt-section-heading,.dt-card > .controls-section-title,.dt-layer-neighbors > strong) {
|
|
252
|
+
display:block; margin:0; padding:10px 14px; border:0; border-bottom:1px solid var(--border); background:var(--dt-card-heading);
|
|
253
|
+
color:var(--ink); font:600 12px/1.4 var(--ui); letter-spacing:0; text-transform:none;
|
|
254
|
+
}
|
|
255
|
+
.devtools-ready .dt-shell .dt-card-table { overflow:auto; }
|
|
256
|
+
.devtools-ready .dt-shell .dt-card-table :is(.dt-table,.dt-layer-rules) { margin:0; }
|
|
257
|
+
.devtools-ready .dt-shell .dt-card-table th { background:var(--dt-card-heading); padding:9px 14px; text-transform:none; letter-spacing:0; }
|
|
258
|
+
.devtools-ready .dt-shell .dt-card-table td { padding:9px 14px; height:36px; border-bottom-color:var(--border); }
|
|
259
|
+
.devtools-ready .dt-shell :is(.dt-card-table,.dt-layer-samples) tr:last-child td { border-bottom:0; }
|
|
260
|
+
.devtools-ready .dt-shell .dt-subtabs { padding-inline:14px; background:var(--paper); }
|
|
261
|
+
|
|
262
|
+
/* Map: each existing section is one card, with the enabled module fields kept inline. */
|
|
263
|
+
.devtools-ready .dt-shell .dt-scene-content { max-width:none; background:var(--dt-page); }
|
|
264
|
+
.devtools-ready .dt-shell .map-controls { padding:14px!important; background:var(--dt-page); }
|
|
265
|
+
.devtools-ready .dt-shell .controls-grid { display:block; border:0; padding:0; }
|
|
266
|
+
.devtools-ready .dt-shell :is(.controls-selection,.controls-runtime) { border:1px solid var(--border); padding:0; }
|
|
267
|
+
.devtools-ready .dt-shell .controls-selection[hidden] { display:none; }
|
|
268
|
+
.devtools-ready .dt-shell .controls-section-title { margin:0; }
|
|
269
|
+
.devtools-ready .dt-shell .map-controls .control-field {
|
|
270
|
+
grid-template-columns:minmax(120px,180px) minmax(0,1fr); min-height:44px; padding:9px 18px; gap:16px;
|
|
271
|
+
}
|
|
272
|
+
.devtools-ready .dt-shell .map-controls .control-field.dt-palette-field { grid-template-columns:minmax(120px,180px) minmax(0,1fr); }
|
|
273
|
+
.devtools-ready .dt-shell :is(.controls-selection,.controls-runtime) > .control-field:last-child { border-bottom:0; }
|
|
274
|
+
.devtools-ready .dt-shell .control-module { background:transparent; }
|
|
275
|
+
.devtools-ready .dt-shell .control-module:last-child { border-bottom:0; }
|
|
276
|
+
.devtools-ready .dt-shell .control-module-header { min-height:44px; padding:10px 18px; }
|
|
277
|
+
.devtools-ready .dt-shell .control-module-header label { gap:10px; }
|
|
278
|
+
.devtools-ready .dt-shell .control-module-header input { margin-right:0; }
|
|
279
|
+
.devtools-ready .dt-shell .control-module fieldset { padding:0; border-top:1px solid var(--border); background:color-mix(in srgb,var(--paper) 65%,var(--surface)); }
|
|
280
|
+
.devtools-ready .dt-shell .control-module .control-field { padding-left:54px; border-bottom:1px solid var(--border); }
|
|
281
|
+
.devtools-ready .dt-shell .control-module .control-field:last-child { border-bottom:0; }
|
|
282
|
+
.devtools-ready .dt-shell .map-controls :is(select,input[type=number]) { border-radius:5px; }
|
|
283
|
+
.devtools-ready .dt-shell :is(.controls-segments,.dt-palettes button) { border-radius:5px; }
|
|
284
|
+
.devtools-ready .dt-shell .dt-palettes { gap:6px; flex-wrap:wrap; }
|
|
285
|
+
.devtools-ready .dt-shell .dt-palettes button { height:auto; min-height:30px; padding:4px 8px; }
|
|
286
|
+
|
|
287
|
+
/* Layers: preserve the list and tabs, frame each detail block as a card. */
|
|
288
|
+
.devtools-ready .dt-shell .dt-layer-content { container-type:inline-size; }
|
|
289
|
+
.devtools-ready .dt-shell :is(.dt-layer-evidence,.dt-layer-neighbors) { padding:0; overflow:hidden; }
|
|
290
|
+
.devtools-ready .dt-shell .dt-layer-evidence-row { padding:10px 14px; grid-template-columns:16px 110px minmax(0,1fr); }
|
|
291
|
+
.devtools-ready .dt-shell .dt-layer-evidence-row:last-child { border-bottom:0; }
|
|
292
|
+
.devtools-ready .dt-shell .dt-layer-neighbors > div { margin:0; padding:9px 14px; border-bottom:1px solid var(--border); }
|
|
293
|
+
.devtools-ready .dt-shell .dt-layer-neighbors > div:last-child { border-bottom:0; }
|
|
294
|
+
.devtools-ready .dt-shell :is(.dt-layer-section,.dt-layer-origin) { padding:12px 14px; }
|
|
295
|
+
.devtools-ready .dt-shell .dt-layer-section h4 { color:var(--ink); font:600 12px/1.4 var(--ui); text-transform:none; letter-spacing:0; }
|
|
296
|
+
.devtools-ready .dt-shell .dt-layer-content > .dt-tools { margin-bottom:12px; padding:0 0 8px; border:0; background:transparent; }
|
|
297
|
+
.devtools-ready .dt-shell .dt-layer-content > .dt-note { padding:0 2px 12px; }
|
|
298
|
+
.devtools-ready .dt-shell .dt-layer-samples { max-height:240px; }
|
|
299
|
+
.devtools-ready .dt-shell .dt-layer-samples td { padding:8px 12px; }
|
|
300
|
+
.devtools-ready .dt-shell .dt-layer-group-title { height:30px; min-height:30px; background:var(--dt-card-heading)!important; }
|
|
301
|
+
.devtools-ready .dt-shell .dt-layer-group-title strong { text-transform:capitalize; letter-spacing:0; }
|
|
302
|
+
.devtools-ready .dt-shell .dt-layer-row { grid-template-columns:18px 26px minmax(0,1fr) 52px; height:36px; min-height:36px; gap:6px; }
|
|
303
|
+
.devtools-ready .dt-shell .dt-layer-glyph { display:grid; place-items:center; width:26px; height:26px; border:1px solid var(--border); border-radius:6px; background:var(--surface); }
|
|
304
|
+
@container(max-width:400px) {
|
|
305
|
+
.devtools-ready .dt-shell .dt-layer-evidence-row { grid-template-columns:16px minmax(0,1fr); gap:4px 8px; }
|
|
306
|
+
.devtools-ready .dt-shell .dt-layer-evidence-row > :last-child { grid-column:2; }
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/* Tiles: keep whole tables together and allow long layer names to wrap. */
|
|
310
|
+
.devtools-ready .dt-shell .dt-tile-content > .dt-note { padding:0 2px 10px; }
|
|
311
|
+
.devtools-ready .dt-shell .dt-tile-content .dt-table td { vertical-align:top; white-space:normal; overflow-wrap:anywhere; }
|
|
312
|
+
.devtools-ready .dt-shell .dt-tile-content .dt-tile-chip { display:inline-block; margin:2px 4px 2px 0; max-width:100%; white-space:normal; overflow-wrap:anywhere; text-align:left; border-radius:4px; font-size:10px; }
|
|
313
|
+
.devtools-ready .dt-shell .dt-tile-content > .dt-kv { padding:0; gap:0; overflow:hidden; grid-template-columns:minmax(100px,35%) minmax(0,1fr); }
|
|
314
|
+
.devtools-ready .dt-shell .dt-tile-content > .dt-kv > * { padding:10px 14px; border-bottom:1px solid var(--border); }
|
|
315
|
+
.devtools-ready .dt-shell .dt-tile-content > .dt-kv > :nth-last-child(-n+2) { border-bottom:0; }
|
|
316
|
+
.devtools-ready .dt-shell .dt-tile-feature { padding:10px 14px; }
|
|
317
|
+
.devtools-ready .dt-shell .dt-tile-content > :is(.dt-tile-chip-list,.dt-tile-paging) { padding:0 0 10px; margin-bottom:10px; background:transparent; }
|
|
318
|
+
|
|
319
|
+
/* Debug: overlay controls and resolution controls retain their existing actions. */
|
|
320
|
+
.devtools-ready .dt-shell .dt-card > .dt-debug-overlay { grid-template-columns:76px minmax(0,1fr) auto; padding:10px 18px; gap:14px; }
|
|
321
|
+
.devtools-ready .dt-shell .dt-debug-overlay:last-child { border-bottom:0; }
|
|
322
|
+
.devtools-ready .dt-shell .dt-debug-preview { width:76px; height:50px; border-radius:6px; }
|
|
323
|
+
.devtools-ready .dt-shell .dt-debug-overlay strong { font-weight:500; }
|
|
324
|
+
.devtools-ready .dt-shell .dt-debug-overlay small { margin-top:3px; font-size:11px; }
|
|
325
|
+
.devtools-ready .dt-shell .dt-overdraw-help { max-width:none; padding:10px 18px 12px 108px; }
|
|
326
|
+
.devtools-ready .dt-shell .dt-debug-scale { padding:12px 18px; gap:12px; }
|
|
327
|
+
.devtools-ready .dt-shell .dt-debug-scale > span:first-child { flex:0 0 180px; }
|
|
328
|
+
.devtools-ready .dt-shell .dt-debug-scale > input[type=range] { width:108px; min-width:60px; }
|
|
329
|
+
.devtools-ready .dt-shell .dt-debug-scale > .dt-debug-specimen { flex:1; min-width:160px; }
|
|
330
|
+
.devtools-ready .dt-shell .dt-card > .dt-note { padding:0 18px 12px; }
|
|
331
|
+
.devtools-ready .dt-shell .dt-debug-scale:last-child { border-top:1px solid var(--border); }
|
|
332
|
+
|
|
333
|
+
/* Assets and findings use the same spacing and surfaces as the other pages. */
|
|
334
|
+
.devtools-ready .dt-shell .dt-assets-content { background:var(--dt-page); }
|
|
335
|
+
.devtools-ready .dt-shell .dt-asset-main { padding:14px; }
|
|
336
|
+
.devtools-ready .dt-shell :is(.dt-sprite-grid,.dt-model-grid,.dt-font-list) { gap:10px; background:transparent; }
|
|
337
|
+
.devtools-ready .dt-shell :is(.dt-sprite-card,.dt-model-card,.dt-font-card) { border:1px solid var(--border); border-radius:8px; background:var(--paper); overflow:hidden; }
|
|
338
|
+
.devtools-ready .dt-shell .dt-sprite-card { padding:10px 8px; }
|
|
339
|
+
.devtools-ready .dt-shell .dt-font-list { padding:14px; max-width:none; }
|
|
340
|
+
.devtools-ready .dt-shell .dt-font-card { padding:12px; }
|
|
341
|
+
.devtools-ready .dt-shell .dt-asset-aside { margin:14px 14px 14px 0; border:1px solid var(--border); border-radius:10px; background:var(--paper); }
|
|
342
|
+
.devtools-ready .dt-shell .dt-asset-aside h3 { margin:0; padding:12px 14px; background:var(--dt-card-heading); border-bottom:1px solid var(--border); }
|
|
343
|
+
.devtools-ready .dt-shell .dt-diagnostic { overflow:hidden; }
|
|
344
|
+
.devtools-ready .dt-shell .dt-diagnostic > summary { padding:12px 14px; }
|
|
345
|
+
.devtools-ready .dt-shell .dt-diagnostic[open] > summary { background:var(--dt-card-heading); border-bottom:1px solid var(--border); }
|
|
346
|
+
.devtools-ready .dt-shell .dt-diagnostic-detail { padding:12px 14px 14px 39px; }
|
|
347
|
+
.devtools-ready .dt-shell .dt-diagnostics-empty { padding:20px; max-width:none; }
|
|
348
|
+
|
|
349
|
+
@container(max-width:760px) {
|
|
350
|
+
.devtools-ready .dt-shell .dt-asset-aside { margin:0 14px 14px; }
|
|
351
|
+
}
|
|
352
|
+
@container(max-width:480px) {
|
|
353
|
+
.devtools-ready .dt-shell :is(.dt-card-page,.dt-layer-content,.dt-tile-content,.dt-diagnostics-list,.dt-asset-main,.dt-font-list) { padding:10px; }
|
|
354
|
+
.devtools-ready .dt-shell .map-controls { padding:10px!important; }
|
|
355
|
+
.devtools-ready .dt-shell .map-controls :is(.control-field,.control-field.dt-palette-field) { grid-template-columns:110px minmax(0,1fr); gap:10px; padding-inline:12px; }
|
|
356
|
+
.devtools-ready .dt-shell .control-module .control-field { padding-left:24px; }
|
|
357
|
+
.devtools-ready .dt-shell .dt-debug-scale > span:first-child { flex-basis:100%; }
|
|
358
|
+
.devtools-ready .dt-shell .dt-card > .dt-debug-overlay { grid-template-columns:60px minmax(0,1fr) auto; padding:10px 12px; gap:10px; }
|
|
359
|
+
.devtools-ready .dt-shell .dt-debug-preview { width:60px; height:40px; }
|
|
360
|
+
.devtools-ready .dt-shell .dt-overdraw-help { padding-left:12px; }
|
|
361
|
+
}
|
|
362
|
+
`;
|
|
363
|
+
|
|
364
|
+
// src/devtools/inspector-styles.ts
|
|
365
|
+
var inspectorStyles = `
|
|
366
|
+
body.devtools-ready {
|
|
367
|
+
--paper:#F8F6F2; --surface:#F0EDE7; --ink:#30251F; --muted:#635449; --faint:#A0907F;
|
|
368
|
+
--border:#E1DCD3; --border-strong:#C7BFB3; --accent:#38668B; --dt-selected:#E6ECF2;
|
|
369
|
+
--hover:#E9E5DE; --accent-soft:#DFE8F0; --ok:#2F704B; --ok-soft:#E3EBDB; --warn:#815D21; --warn-soft:#F3E6CF;
|
|
370
|
+
--danger:#B13C34; --dt-string:var(--ink); --dt-number:var(--accent); --mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
|
|
371
|
+
--ui:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;
|
|
372
|
+
--dt-control-height:22px; --dt-row-height:22px; --dt-nav-height:27px;
|
|
373
|
+
--dt-header-height:44px; --dt-camera-control-height:30px;
|
|
374
|
+
color-scheme:light; font:12px/1.35 var(--ui); -webkit-font-smoothing:antialiased;
|
|
375
|
+
}
|
|
376
|
+
body.devtools-ready[data-inspector-theme=dark] {
|
|
377
|
+
--paper:#1D1816; --surface:#171311; --ink:#F5EDD9; --muted:#C3B7A9; --faint:#857869; --border:#40352F; --border-strong:#5A4B43;
|
|
378
|
+
--accent:#83ADD1; --dt-selected:#22303B; --hover:#2E2622; --accent-soft:#223039; --ok:#91B887; --ok-soft:#24301F; --warn:#E2B36F; --warn-soft:#3A2F20; --danger:#F1746B; color-scheme:dark;
|
|
379
|
+
}
|
|
380
|
+
@media(prefers-color-scheme:dark) { body.devtools-ready[data-inspector-theme=system] {
|
|
381
|
+
--paper:#1D1816; --surface:#171311; --ink:#F5EDD9; --muted:#C3B7A9; --faint:#857869; --border:#40352F; --border-strong:#5A4B43;
|
|
382
|
+
--accent:#83ADD1; --dt-selected:#22303B; --hover:#2E2622; --accent-soft:#223039; --ok:#91B887; --ok-soft:#24301F; --warn:#E2B36F; --warn-soft:#3A2F20; --danger:#F1746B; color-scheme:dark;
|
|
383
|
+
}}
|
|
384
|
+
.devtools-ready button,.devtools-ready input,.devtools-ready select { font:inherit; }
|
|
385
|
+
.devtools-ready button { border:1px solid var(--border); border-radius:6px; min-height:28px; padding:4px 10px; cursor:pointer; }
|
|
386
|
+
.devtools-ready button:disabled { cursor:default; opacity:.45; }
|
|
387
|
+
.devtools-ready button:hover:not(:disabled) { background:var(--hover); }
|
|
388
|
+
.devtools-ready input[type=range] { accent-color:var(--accent); }
|
|
389
|
+
.devtools-ready code,.devtools-ready pre { font-family:var(--mono); }
|
|
390
|
+
.devtools-ready .dt-head { height:40px; min-height:40px; flex-wrap:nowrap; gap:2px; padding:0 8px; background:var(--paper); }
|
|
391
|
+
.devtools-ready .dt-head-main { display:flex; align-items:center; flex:1; min-width:0; }
|
|
392
|
+
.devtools-ready .dt-tabs { flex:1; align-items:center; gap:2px; }
|
|
393
|
+
.devtools-ready .dt-tabs button { border:0; border-radius:6px; min-height:28px; padding:5px 10px; color:var(--muted); background:transparent; }
|
|
394
|
+
.devtools-ready .dt-tabs button[aria-selected=true] { color:var(--ink); background:var(--surface); }
|
|
395
|
+
.devtools-ready .dt-head > .dt-icon { width:28px; height:28px; margin:0; }
|
|
396
|
+
.devtools-ready .dt-icon[aria-pressed=true] { background:var(--accent-soft); color:var(--accent); }
|
|
397
|
+
.devtools-ready .dt-camera { display:flex; align-items:center; flex:0 0 auto; flex-wrap:wrap; gap:6px; margin:0; padding:8px 12px; border:0; border-bottom:1px solid var(--border); background:var(--paper); overflow:visible; }
|
|
398
|
+
.devtools-ready .dt-camera-field { display:flex; flex:0 0 auto; gap:0; height:30px; border:1px solid var(--border); border-radius:6px; background:var(--surface); overflow:hidden; }
|
|
399
|
+
.devtools-ready .dt-camera-field > span:first-child { padding:0 4px 0 9px; color:var(--faint); font-size:10px; text-transform:uppercase; letter-spacing:.04em; }
|
|
400
|
+
.devtools-ready .dt-camera-field input[type=number] { width:58px; min-height:0; padding:0 5px; border:0; background:transparent; font:12px var(--mono); appearance:textfield; color:var(--ink); }
|
|
401
|
+
.devtools-ready .dt-camera-field input::-webkit-inner-spin-button { appearance:none; }
|
|
402
|
+
.devtools-ready .dt-camera-field input[type=range] { width:110px; padding:0; margin:0 10px 0 2px; min-height:0; }
|
|
403
|
+
.devtools-ready .dt-unit { padding-right:9px; color:var(--faint); }
|
|
404
|
+
.devtools-ready .dt-copy-agent { margin-left:auto; background:var(--ink); color:var(--paper); border-color:var(--ink); font-weight:500; }
|
|
405
|
+
.devtools-ready .dt-copy-agent:hover { background:var(--ink); opacity:.88; }
|
|
406
|
+
.devtools-ready .dt-menu { top:38px; right:8px; width:236px; padding:10px; border-radius:10px; box-shadow:0 8px 28px #0002; }
|
|
407
|
+
.devtools-ready .dt-dock-options { display:grid; grid-template-columns:repeat(3,1fr); gap:6px; margin-bottom:8px; }
|
|
408
|
+
.devtools-ready .dt-dock-options button { width:auto; height:34px; border:1px solid var(--border); justify-content:center; }
|
|
409
|
+
.devtools-ready .dt-menu-label { font-size:11px; }
|
|
410
|
+
.devtools-ready .dt-segments,.devtools-ready .dt-layer-modes,.devtools-ready .controls-segments { display:inline-flex; padding:2px; background:var(--surface); border:1px solid var(--border); border-radius:6px; gap:2px; }
|
|
411
|
+
.devtools-ready .dt-segments button,.devtools-ready .dt-layer-modes button,.devtools-ready .controls-segments button { min-height:23px; border:0; border-radius:4px; padding:3px 9px; font-size:12px; background:transparent; color:var(--muted); }
|
|
412
|
+
.devtools-ready .dt-segments button[aria-pressed=true],.devtools-ready .dt-layer-modes button[aria-pressed=true],.devtools-ready .controls-segments button[aria-checked=true] { color:var(--ink); background:var(--paper); box-shadow:0 1px 2px #0001; }
|
|
413
|
+
.devtools-ready .dt-menu .dt-segments { display:flex; }.devtools-ready .dt-menu .dt-segments button { flex:1; }
|
|
414
|
+
.devtools-ready .dt-tools { padding:10px 12px; gap:8px; background:var(--paper); }
|
|
415
|
+
.devtools-ready .dt-tools input[type=search] { height:30px; padding:0 9px; background:var(--surface); border-radius:6px; width:auto; }
|
|
416
|
+
.devtools-ready .dt-subtabs { padding:0 20px; gap:18px; }
|
|
417
|
+
.devtools-ready .dt-subtabs button { background:transparent; border:0; border-radius:0; border-bottom:2px solid transparent; padding:8px 0 9px; color:var(--muted); min-height:36px; }
|
|
418
|
+
.devtools-ready .dt-subtabs button[aria-selected=true] { color:var(--ink); border-bottom-color:var(--ink); }
|
|
419
|
+
.devtools-ready input[role=switch] { appearance:none; width:26px!important; height:15px!important; min-height:0; flex:none; border:0; border-radius:8px; background:var(--faint); position:relative; padding:0; margin:0; cursor:pointer; }
|
|
420
|
+
.devtools-ready input[role=switch]::before { content:''; position:absolute; width:11px; height:11px; left:2px; top:2px; border-radius:50%; background:#fff; transition:transform .15s; }
|
|
421
|
+
.devtools-ready input[role=switch]:checked { background:var(--accent); }
|
|
422
|
+
.devtools-ready input[role=switch]:checked::before { transform:translateX(11px); }
|
|
423
|
+
.devtools-ready .dt-layer-split { display:grid; grid-template-columns:minmax(0,min(38%,360px)) minmax(0,1fr); }
|
|
424
|
+
.devtools-ready .dt-layer-sidebar { display:flex; flex-direction:column; min-width:0; min-height:0; overflow:hidden; border-right:1px solid var(--border); }
|
|
425
|
+
.devtools-ready .dt-layer-list { flex:1; border:0; }.devtools-ready .dt-layer-tools { flex-direction:column; align-items:stretch; }
|
|
426
|
+
.devtools-ready .dt-layer-tools-row { display:flex; align-items:center; gap:8px; }.devtools-ready .dt-layer-total { margin-left:auto; font:11px var(--mono); }
|
|
427
|
+
.devtools-ready .dt-layer-visible { font-size:12px; }.devtools-ready .dt-layer-group-title { height:32px; font-size:11px; padding:0 12px 0 8px!important; }
|
|
428
|
+
.devtools-ready .dt-layer-group-title strong { text-transform:uppercase; letter-spacing:.05em; }
|
|
429
|
+
.devtools-ready .dt-layer-row { display:grid; grid-template-columns:20px 18px minmax(0,1fr) 64px; height:30px; padding:0 12px 0 6px; gap:8px; border-left:2px solid transparent; }
|
|
430
|
+
.devtools-ready .dt-layer-row[aria-pressed=true] { background:var(--dt-selected); border-left-color:var(--accent); color:var(--ink); }
|
|
431
|
+
.devtools-ready .dt-layer-glyph { color:var(--muted); font-size:15px; }.devtools-ready .dt-layer-label { font-size:12px; }
|
|
432
|
+
.devtools-ready .dt-layer-prefix { color:var(--faint); }.devtools-ready .dt-layer-name { font-weight:500; }
|
|
433
|
+
.devtools-ready .dt-eye { padding:0; border:0; width:20px; height:20px; min-height:20px; color:var(--faint); background:transparent; display:grid; place-items:center; opacity:0; }
|
|
434
|
+
.devtools-ready .dt-eye svg { width:14px; height:14px; }
|
|
435
|
+
.devtools-ready :is(.dt-layer-row,.dt-layer-group-title):hover .dt-eye,.devtools-ready .dt-eye:focus-visible,.devtools-ready .dt-eye[aria-pressed=true] { opacity:1; }
|
|
436
|
+
.dt-zrange { position:relative; display:block; width:64px; height:14px; }.dt-zrange::before { content:''; position:absolute; inset:6px 0; height:2px; background:var(--border); }
|
|
437
|
+
.dt-zrange i { position:absolute; top:5px; height:4px; border-radius:2px; background:var(--accent); opacity:.65; }.dt-zrange em { position:absolute; top:1px; height:12px; width:1px; background:var(--accent); }
|
|
438
|
+
.devtools-ready .dt-layer-heading { padding:14px 20px 6px; align-items:flex-start; border:0; }
|
|
439
|
+
.devtools-ready .dt-layer-title strong { font-size:16px; font-weight:600; }.devtools-ready .dt-layer-title > code { font-size:10px; color:var(--faint); }
|
|
440
|
+
.dt-layer-status { display:flex; align-items:center; gap:8px; font-size:12px; color:var(--muted); margin-top:6px; }.dt-layer-status[data-state=Enabled] { color:var(--ok); }.dt-layer-status[data-state="Outside zoom"] { color:var(--warn); }
|
|
441
|
+
.devtools-ready .dt-layer-rules { font-size:12px; }.devtools-ready .dt-layer-rules th { padding:8px 20px; font-size:11px; text-transform:uppercase; letter-spacing:.05em; }
|
|
442
|
+
.devtools-ready .dt-layer-rules th:first-child { width:42%; }.devtools-ready .dt-layer-rules td { padding:8px 20px; vertical-align:middle; min-height:44px; }
|
|
443
|
+
.devtools-ready .dt-layer-rules code { font:12px/1.5 var(--mono); }.devtools-ready .dt-layer-rule-name { color:var(--ink); }.devtools-ready .dt-layer-dependency { color:var(--accent); font:10px var(--mono); }
|
|
444
|
+
.devtools-ready .dt-layer-rule-editor { gap:9px; flex-wrap:wrap; }.devtools-ready .dt-layer-rule-editor input:not([type=color]):not([type=checkbox]):not([type=range]),.devtools-ready .dt-layer-rule-editor select { min-height:26px; border:1px solid var(--border); font:12px var(--mono); border-radius:6px; }
|
|
445
|
+
.devtools-ready .dt-layer-rule-editor input[type=color] { width:22px; height:22px; }
|
|
446
|
+
.devtools-ready .dt-layer-rule-editor input[type=range] { width:110px; min-width:70px; padding:0; flex:0 1 110px; }
|
|
447
|
+
.dt-rule-toggle { display:flex; align-items:center; gap:10px; }.dt-rule-toggle input { width:14px; height:14px; min-height:0; margin:0; accent-color:var(--accent); }
|
|
448
|
+
.dt-rule-choices { display:inline-flex; flex-wrap:wrap; border:1px solid var(--border); border-radius:6px; overflow:hidden; }.devtools-ready .dt-rule-choices button { min-height:26px; border:0; border-radius:0; font:11px var(--mono); padding:3px 8px; }
|
|
449
|
+
.dt-rule-spark { display:flex; align-items:center; gap:10px; color:var(--accent); font:11px var(--mono); margin-bottom:5px; }.dt-rule-spark svg { width:130px; height:26px; overflow:visible; }
|
|
450
|
+
.dt-filter-chips { display:flex; flex-wrap:wrap; gap:6px; margin-block:10px; }.dt-filter-chip { padding:4px 9px; background:var(--surface); border:1px solid var(--border); border-radius:13px; font:11px var(--mono); }
|
|
451
|
+
.devtools-ready .dt-layer-section,.devtools-ready .dt-layer-origin { padding:12px 20px; }.devtools-ready .dt-layer-section h4 { font-size:11px; text-transform:uppercase; letter-spacing:.05em; color:var(--muted); }
|
|
452
|
+
.devtools-ready .dt-layer-evidence { padding:0; }.devtools-ready .dt-layer-evidence-row { padding:12px 20px; font-size:12px; grid-template-columns:20px 130px 1fr; }
|
|
453
|
+
.devtools-ready .dt-layer-evidence-row[data-state=pass] .dt-layer-indicator { color:var(--ok); }.devtools-ready .dt-layer-evidence-row[data-state=warn] .dt-layer-indicator { color:var(--warn); }
|
|
454
|
+
.devtools-ready .dt-layer-json { font:11px/1.6 var(--mono); color:var(--ink); }
|
|
455
|
+
.devtools-ready .dt-table { font:12px var(--mono); }.devtools-ready .dt-table th { padding:8px 12px; font:11px Inter,system-ui,sans-serif; text-transform:uppercase; border-right:0; }.devtools-ready .dt-table td { height:32px; padding:6px 12px; }.devtools-ready .dt-table tbody tr:nth-child(even) { background:transparent; }
|
|
456
|
+
.devtools-ready .dt-network-body { display:grid; grid-template-columns:minmax(0,min(38%,360px)) minmax(0,1fr); }.dt-tiles-sidebar { display:flex; flex-direction:column; min-width:0; min-height:0; overflow:hidden; border-right:1px solid var(--border); }.devtools-ready .dt-network-detail { border:0; min-width:0; }
|
|
457
|
+
.devtools-ready .dt-tile-heading { padding:14px 20px 8px; }.dt-tile-heading strong { font:15px var(--mono); }.dt-tile-status { padding:0 20px 10px; color:var(--muted); font-size:12px; }
|
|
458
|
+
.dt-tile-grid { display:grid; gap:3px; padding:12px; }.devtools-ready .dt-tile-cell { aspect-ratio:1; padding:0; font:10px var(--mono); min-width:0; min-height:20px; border-radius:4px; background:color-mix(in srgb,var(--accent) var(--weight,10%),var(--paper)); }
|
|
459
|
+
.devtools-ready .dt-tile-cell[aria-pressed=true] { border-color:var(--accent); box-shadow:0 0 0 2px var(--accent-soft); }.dt-tile-grid-title { margin:10px 12px 0; color:var(--muted); font-size:11px; }
|
|
460
|
+
.dt-tile-chip-list { display:flex; gap:6px; flex-wrap:wrap; padding:10px 20px; border-bottom:1px solid var(--border); }.devtools-ready .dt-tile-chip { font:11px var(--mono); border-radius:13px; }
|
|
461
|
+
.dt-asset-layout { display:grid; grid-template-columns:minmax(0,1fr) 300px; min-height:0; flex:1; }.dt-asset-main { min-width:0; overflow:auto; padding:12px; }.dt-asset-aside { overflow:auto; border-left:1px solid var(--border); }.dt-asset-aside h3 { font:500 15px var(--mono); margin:16px 20px; }.dt-asset-variants { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:1px; background:var(--border); }.dt-asset-variant { padding:20px 10px; display:flex; flex-direction:column; align-items:center; gap:10px; background:var(--surface); font:10px var(--mono); overflow-wrap:anywhere; }.dt-asset-variant[data-night=true] { background:#2e2622; color:#f5edd9; }
|
|
462
|
+
.devtools-ready .dt-sprite-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(104px,1fr)); gap:4px; }.devtools-ready .dt-sprite-card { border:1px solid transparent; border-radius:8px; background:transparent; padding:16px 6px 10px; cursor:pointer; }.dt-sprite-card[aria-pressed=true] { background:var(--dt-selected)!important; border-color:var(--accent)!important; }
|
|
463
|
+
.dt-debug-overlay { display:grid; grid-template-columns:84px 1fr auto; align-items:center; gap:16px; padding:10px 20px; border-bottom:1px solid var(--border); cursor:pointer; }.dt-debug-overlay:hover { background:var(--hover); }.dt-debug-overlay small { display:block; color:var(--muted); font-size:12px; }.dt-debug-preview { width:84px; height:58px; border-radius:6px; overflow:hidden; }.dt-debug-preview svg { width:100%; height:100%; }
|
|
464
|
+
.dt-section-heading { background:var(--surface); padding:8px 20px; font-size:11px; font-weight:600; letter-spacing:.05em; text-transform:uppercase; border-bottom:1px solid var(--border); }
|
|
465
|
+
.devtools-ready .dt-overdraw-help { margin:0; border:0; border-bottom:1px solid var(--border); border-radius:0; padding:12px 20px 12px 120px; background:var(--surface); }.dt-debug-scale { display:flex; align-items:center; gap:12px; padding:16px 20px; flex-wrap:wrap; }.dt-debug-scale input[type=number] { width:64px; }
|
|
466
|
+
.devtools-ready .map-controls { padding:0!important; background:var(--paper); }.devtools-ready .controls-heading { display:none; }.devtools-ready .controls-grid { display:block; }.devtools-ready .controls-selection,.devtools-ready .controls-runtime { border:0; padding:0; }
|
|
467
|
+
.devtools-ready .controls-section-title { margin:0; padding:8px 20px; background:var(--surface); border-bottom:1px solid var(--border); font-size:11px; letter-spacing:.05em; }.devtools-ready .control-field { display:grid; grid-template-columns:200px 1fr; align-items:center; margin:0; padding:10px 20px; border-bottom:1px solid var(--border); gap:16px; font-size:13px; }.devtools-ready .control-field select { max-width:100%; width:max-content; min-width:132px; }
|
|
468
|
+
.devtools-ready #controls-modules { display:block; }.devtools-ready .control-module { border:0; border-bottom:1px solid var(--border); border-radius:0; margin:0; background:var(--paper); }.devtools-ready .control-module-header { padding:10px 20px; }.devtools-ready .control-module-header label { justify-content:flex-start; font-size:13px; font-weight:500; }.devtools-ready .control-module-header input { order:-1; margin-right:12px; }.devtools-ready .control-module fieldset { padding:0; background:var(--surface); }.devtools-ready .control-module .control-field { padding-left:58px; border:0; }.devtools-ready .dt-scene-content { max-width:1000px; width:100%; }.dt-palettes { display:flex; gap:8px; flex-wrap:wrap; }.devtools-ready .dt-palette { display:flex; align-items:center; gap:8px; padding:5px 10px 5px 5px; }.dt-palette-swatches { display:grid; grid-template-columns:repeat(2,11px); grid-template-rows:repeat(2,11px); overflow:hidden; border-radius:4px; }
|
|
469
|
+
@container(max-width:760px) {
|
|
470
|
+
.devtools-ready .dt-layer-title { flex-basis:180px; }.devtools-ready .dt-camera-field input[type=range] { width:80px; }
|
|
471
|
+
.devtools-ready .dt-layer-rules th:first-child { width:45%; }.devtools-ready .dt-layer-rules td { padding:8px 12px; }
|
|
472
|
+
.dt-asset-layout { grid-template-columns:1fr; }.dt-asset-aside { border:0; border-top:1px solid var(--border); }
|
|
473
|
+
.devtools-ready .control-field { grid-template-columns:160px 1fr; }.devtools-ready .control-module .control-field { padding-left:30px; }
|
|
474
|
+
}
|
|
475
|
+
@media(prefers-reduced-motion:reduce) { .devtools-ready * { scroll-behavior:auto!important; transition:none!important; animation:none!important; } }
|
|
476
|
+
|
|
477
|
+
.dt-palettes{display:flex;gap:8px;flex-wrap:wrap}.devtools-ready .dt-palettes button{display:flex;flex-direction:column;gap:7px;padding:10px 14px;min-width:110px;background:var(--paper);text-align:left;text-transform:capitalize}.devtools-ready .dt-palettes button[aria-checked=true]{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent-soft)}.dt-palette-swatches{display:flex;height:26px;width:100%;overflow:hidden;border-radius:4px}.dt-palette-swatches i{flex:1;min-width:20px}.devtools-ready .dt-palette-field{align-items:flex-start;flex-direction:column}.dt-model-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(170px,1fr));gap:10px}.devtools-ready .dt-model-card{display:flex;flex-direction:column;align-items:center;padding:0;overflow:hidden;border-radius:10px}.dt-model-card canvas{background:linear-gradient(var(--surface),var(--paper));max-width:100%;height:auto!important}.dt-model-card strong{font:500 12px var(--mono);padding:9px 12px 2px;overflow-wrap:anywhere}.dt-model-card small{font-size:11px;color:var(--muted);padding:0 12px 9px}.dt-model-card[aria-pressed=true]{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent-soft)}.dt-asset-aside>canvas{display:block;margin:auto;max-width:100%;touch-action:none;cursor:grab}.dt-font-ranges{display:flex;align-items:center;gap:6px;flex-wrap:wrap}.dt-font-ranges small{color:var(--muted);font-size:11px}
|
|
478
|
+
|
|
479
|
+
.dt-debug-specimen{display:flex;align-items:center;gap:18px;min-height:70px;padding:12px 20px;border:1px solid var(--border);border-radius:8px;background:var(--surface)}.dt-rule-font-preview{overflow:hidden;max-width:100%}.dt-rule-font-preview canvas{max-width:100%;height:auto!important}
|
|
480
|
+
/* Keep the new shell independent of the legacy in-header camera and toolbar breakpoints. */
|
|
481
|
+
.devtools-ready .dt-shell>.dt-camera{order:0;flex:0 0 auto;margin:0;border-top:0;background:var(--paper);padding:8px 12px}
|
|
482
|
+
.devtools-ready .dt-shell>.dt-head{flex-wrap:nowrap}.devtools-ready .dt-shell .dt-head-main{display:flex;gap:2px}
|
|
483
|
+
.devtools-ready .dt-shell .dt-layer-tools input[type=search]{order:0;flex:0 0 auto;width:100%;height:30px}
|
|
484
|
+
.devtools-ready .dt-shell .dt-layer-tools{flex-wrap:nowrap}.devtools-ready .dt-shell .dt-layer-tools-row{flex:none;min-height:28px}
|
|
485
|
+
.devtools-ready .dt-shell .dt-layer-row{display:grid;grid-template-columns:20px 18px minmax(0,1fr) 64px;height:30px;min-height:30px;padding:0 12px 0 6px;gap:8px}
|
|
486
|
+
.devtools-ready .dt-shell .dt-layer-label{display:block;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
|
|
487
|
+
.devtools-ready .dt-shell .dt-layer-heading{flex:none}.devtools-ready .dt-shell .dt-layer-title{flex:1 1 180px}
|
|
488
|
+
.devtools-ready .dt-shell .dt-layer-sidebar .dt-layer-list{flex:1;min-height:0}.devtools-ready .dt-shell .map-controls .control-field{display:grid;grid-template-columns:160px minmax(0,1fr);padding:12px 20px;margin:0}
|
|
489
|
+
.devtools-ready .dt-shell .control-module-header label{display:flex;gap:12px}.devtools-ready .dt-shell .map-controls input[role=switch]{width:26px!important;height:15px!important;min-height:0;border:0;background:var(--faint)}.devtools-ready .dt-shell .map-controls input[role=switch]:checked{background:var(--accent)}.devtools-ready .dt-shell .map-controls input[role=switch]::before{width:11px;height:11px;top:2px;left:2px;background:white}.devtools-ready .dt-shell .map-controls input[role=switch]:checked::before{transform:translateX(11px)}
|
|
490
|
+
@container(max-width:760px){.devtools-ready .dt-shell .dt-layer-tools{padding:8px 12px}.devtools-ready .dt-shell .dt-tabs button{padding-inline:7px}.devtools-ready .dt-shell .dt-head{padding-inline:4px}.devtools-ready .dt-shell .dt-camera-field{flex:0 0 auto}}
|
|
491
|
+
|
|
492
|
+
/* Shared density also wins over panel styles injected after the shell stylesheet. */
|
|
493
|
+
.devtools-ready .dt-shell { font:12px/1.35 var(--ui); }
|
|
494
|
+
.devtools-ready .dt-shell :is(button,input,select,summary) { min-height:var(--dt-control-height); }
|
|
495
|
+
.devtools-ready .dt-shell button { padding:2px 6px; border-radius:3px; }
|
|
496
|
+
.devtools-ready .dt-shell :is(input[type=checkbox],input[type=radio],input[type=range],input[type=color]) { min-height:0; }
|
|
497
|
+
.devtools-ready .dt-shell .dt-head { height:var(--dt-header-height); min-height:var(--dt-header-height); padding:0 4px; }
|
|
498
|
+
.devtools-ready .dt-shell .dt-tabs { gap:0; }
|
|
499
|
+
.devtools-ready .dt-shell .dt-tabs button { height:34px; min-height:34px; padding:0 8px; border-radius:4px; }
|
|
500
|
+
.devtools-ready .dt-shell .dt-head .dt-icon { width:30px; height:30px; min-height:30px; padding:5px; margin-block:0; }
|
|
501
|
+
.devtools-ready .dt-shell > .dt-camera { min-height:48px; padding:8px; gap:6px; }
|
|
502
|
+
.devtools-ready .dt-shell .dt-camera-field { height:var(--dt-camera-control-height); border-radius:5px; }
|
|
503
|
+
.devtools-ready .dt-shell .dt-camera-field > span:first-child { padding-left:6px; }
|
|
504
|
+
.devtools-ready .dt-shell .dt-camera-field input[type=number] { width:48px; min-height:0; padding:0 3px; font-size:12px; }
|
|
505
|
+
.devtools-ready .dt-shell .dt-camera-field input[type=range] { width:88px; margin:0 6px 0 2px; }
|
|
506
|
+
.devtools-ready .dt-shell .dt-unit { padding-right:6px; }
|
|
507
|
+
.devtools-ready .dt-shell .dt-camera-reset { display:inline-flex; align-items:center; justify-content:center; flex:none; width:26px; height:100%; min-height:0; padding:4px; border:0; border-left:1px solid var(--border); border-radius:0; background:transparent; }
|
|
508
|
+
.devtools-ready .dt-shell .dt-camera-reset:focus-visible { outline:2px solid var(--accent); outline-offset:-2px; }
|
|
509
|
+
.devtools-ready .dt-shell .dt-copy-agent { min-height:var(--dt-camera-control-height); padding:4px 10px; font-size:12px; white-space:nowrap; }
|
|
510
|
+
.devtools-ready .dt-shell .dt-menu { top:var(--dt-header-height); right:4px; width:216px; padding:6px; border-radius:4px; }
|
|
511
|
+
.devtools-ready .dt-shell .dt-dock-options { gap:4px; margin-bottom:6px; }
|
|
512
|
+
.devtools-ready .dt-shell .dt-dock-options button { height:26px; padding:2px; }
|
|
513
|
+
.devtools-ready .dt-shell :is(.dt-segments,.dt-layer-modes,.controls-segments) { padding:1px; gap:1px; border-radius:3px; }
|
|
514
|
+
.devtools-ready .dt-shell :is(.dt-segments,.dt-layer-modes,.controls-segments) button { min-height:20px; padding:1px 6px; border-radius:2px; }
|
|
515
|
+
.devtools-ready .dt-shell .dt-tools { min-height:28px; padding:3px 8px; gap:4px; }
|
|
516
|
+
.devtools-ready .dt-shell .dt-tools input[type=search] { height:var(--dt-control-height); padding:0 6px; border-radius:3px; }
|
|
517
|
+
.devtools-ready .dt-shell .dt-layer-tools-row { min-width:0; min-height:var(--dt-control-height); gap:6px; flex-wrap:wrap; }
|
|
518
|
+
.devtools-ready .dt-shell .dt-layer-modes { max-width:100%; overflow-x:auto; }
|
|
519
|
+
.devtools-ready .dt-shell .dt-subtabs { padding:0 8px; gap:12px; }
|
|
520
|
+
.devtools-ready .dt-shell .dt-subtabs button { min-height:var(--dt-nav-height); padding:3px 0; border-radius:0; }
|
|
521
|
+
.devtools-ready .dt-shell .dt-footer { min-height:20px; padding:2px 8px; gap:8px; }
|
|
522
|
+
.devtools-ready .dt-shell .dt-section-heading { padding:4px 10px; }
|
|
523
|
+
.devtools-ready .dt-shell .dt-kv { gap:3px 8px; padding:6px 8px; font:11px/1.4 var(--mono); }
|
|
524
|
+
.devtools-ready .dt-shell .dt-layer-group-title { height:var(--dt-row-height); min-height:var(--dt-row-height); padding:0 8px 0 6px!important; }
|
|
525
|
+
.devtools-ready .dt-shell .dt-layer-row { grid-template-columns:18px 16px minmax(0,1fr) 52px; height:var(--dt-row-height); min-height:var(--dt-row-height); padding:0 8px 0 4px; gap:4px; border-radius:0; }
|
|
526
|
+
.devtools-ready .dt-shell .dt-layer-glyph { font-size:13px; }
|
|
527
|
+
.devtools-ready .dt-shell .dt-eye { min-height:18px; width:18px; height:18px; padding:0; }
|
|
528
|
+
.devtools-ready .dt-shell .dt-zrange { width:52px; }
|
|
529
|
+
.devtools-ready .dt-shell .dt-layer-heading { padding:8px 10px 4px; }
|
|
530
|
+
.devtools-ready .dt-shell .dt-layer-title strong { font-size:13px; }
|
|
531
|
+
.devtools-ready .dt-shell .dt-layer-status { margin-top:3px; gap:6px; font-size:11px; }
|
|
532
|
+
.devtools-ready .dt-shell .dt-layer-rules th { padding:4px 10px; }
|
|
533
|
+
.devtools-ready .dt-shell .dt-layer-rules td { padding:3px 10px; min-height:24px; }
|
|
534
|
+
.devtools-ready .dt-shell .dt-layer-rules code { font:11px/1.4 var(--mono); }
|
|
535
|
+
.devtools-ready .dt-shell .dt-layer-rule-editor { gap:5px; }
|
|
536
|
+
.devtools-ready .dt-shell .dt-layer-rule-editor :is(input:not([type=color]):not([type=checkbox]):not([type=range]),select) { min-height:20px; font:11px/1.4 var(--mono); border-radius:3px; }
|
|
537
|
+
.devtools-ready .dt-shell .dt-layer-rule-editor input[type=color] { width:18px; height:20px; }
|
|
538
|
+
.devtools-ready .dt-shell .dt-layer-rule-editor input[type=range] { width:88px; min-width:60px; flex-basis:88px; }
|
|
539
|
+
.devtools-ready .dt-shell .dt-rule-toggle { gap:6px; }
|
|
540
|
+
.devtools-ready .dt-shell .dt-rule-choices { border-radius:3px; }
|
|
541
|
+
.devtools-ready .dt-shell .dt-rule-choices button { min-height:20px; padding:1px 5px; border-radius:0; }
|
|
542
|
+
.devtools-ready .dt-shell .dt-rule-spark { gap:6px; margin-bottom:3px; }
|
|
543
|
+
.devtools-ready .dt-shell .dt-rule-spark svg { width:100px; height:22px; }
|
|
544
|
+
.devtools-ready .dt-shell .dt-filter-chips { gap:4px; margin-block:6px; }
|
|
545
|
+
.devtools-ready .dt-shell .dt-filter-chip { padding:2px 6px; }
|
|
546
|
+
.devtools-ready .dt-shell :is(.dt-layer-section,.dt-layer-origin) { padding:6px 10px; }
|
|
547
|
+
.devtools-ready .dt-shell .dt-layer-evidence-row { padding:6px 10px; grid-template-columns:16px 110px minmax(0,1fr); }
|
|
548
|
+
.devtools-ready .dt-shell .dt-table { font:11px/1.4 var(--mono); }
|
|
549
|
+
.devtools-ready .dt-shell .dt-table th { padding:4px 8px; font:11px var(--ui); }
|
|
550
|
+
.devtools-ready .dt-shell .dt-table td { height:var(--dt-row-height); padding:2px 8px; }
|
|
551
|
+
.devtools-ready .dt-shell .dt-tile-heading { padding:8px 10px 4px; }
|
|
552
|
+
.devtools-ready .dt-shell .dt-tile-heading strong { font-size:13px; }
|
|
553
|
+
.devtools-ready .dt-shell .dt-tile-status { padding:0 10px 6px; }
|
|
554
|
+
.devtools-ready .dt-shell .dt-tile-grid { padding:8px; gap:2px; }
|
|
555
|
+
.devtools-ready .dt-shell .dt-tile-cell { padding:0; min-height:20px; }
|
|
556
|
+
.devtools-ready .dt-shell .dt-tile-grid-title { margin:6px 8px 0; }
|
|
557
|
+
.devtools-ready .dt-shell .dt-tile-chip-list { padding:5px 10px; gap:4px; }
|
|
558
|
+
.devtools-ready .dt-shell .dt-asset-main { padding:8px; }
|
|
559
|
+
.devtools-ready .dt-shell .dt-asset-aside h3 { font-size:13px; margin:8px 10px; }
|
|
560
|
+
.devtools-ready .dt-shell .dt-asset-variant { padding:10px 6px; gap:6px; }
|
|
561
|
+
.devtools-ready .dt-shell .dt-sprite-grid { grid-template-columns:repeat(auto-fill,minmax(88px,1fr)); }
|
|
562
|
+
.devtools-ready .dt-shell .dt-sprite-card { padding:6px 4px; border-radius:4px; }
|
|
563
|
+
.devtools-ready .dt-shell .dt-sprite-preview { height:44px; }
|
|
564
|
+
.devtools-ready .dt-shell .dt-sprite-card figcaption { margin-top:4px; }
|
|
565
|
+
.devtools-ready .dt-shell .dt-font-card { padding:8px; }
|
|
566
|
+
.devtools-ready .dt-shell .dt-font-card figcaption { margin-bottom:5px; font:11px/1.4 var(--mono); }
|
|
567
|
+
.devtools-ready .dt-shell .dt-font-loading { padding:8px; }
|
|
568
|
+
.devtools-ready .dt-shell .dt-model-grid { grid-template-columns:repeat(auto-fill,minmax(140px,1fr)); gap:6px; }
|
|
569
|
+
.devtools-ready .dt-shell .dt-model-card { padding:0; border-radius:4px; }
|
|
570
|
+
.devtools-ready .dt-shell .dt-model-card strong { padding:5px 8px 2px; }
|
|
571
|
+
.devtools-ready .dt-shell .dt-model-card small { padding:0 8px 5px; }
|
|
572
|
+
.devtools-ready .dt-shell .dt-debug-overlay { grid-template-columns:60px 1fr auto; gap:8px; padding:6px 10px; }
|
|
573
|
+
.devtools-ready .dt-shell .dt-debug-preview { width:60px; height:40px; border-radius:3px; }
|
|
574
|
+
.devtools-ready .dt-shell .dt-overdraw-help { padding:6px 10px 6px 78px; }
|
|
575
|
+
.devtools-ready .dt-shell .dt-debug-scale { gap:8px; padding:8px 10px; }
|
|
576
|
+
.devtools-ready .dt-shell .dt-debug-specimen { gap:10px; min-height:52px; padding:6px 10px; border-radius:4px; }
|
|
577
|
+
.devtools-ready .dt-shell .controls-section-title { padding:4px 10px; }
|
|
578
|
+
.devtools-ready .dt-shell .map-controls .control-field { grid-template-columns:140px minmax(0,1fr); padding:6px 10px; gap:8px; font-size:12px; }
|
|
579
|
+
.devtools-ready .dt-shell .control-module-header { padding:6px 10px; }
|
|
580
|
+
.devtools-ready .dt-shell .control-module-header label { font-size:12px; gap:6px; }
|
|
581
|
+
.devtools-ready .dt-shell .control-module-header input { margin-right:6px; }
|
|
582
|
+
.devtools-ready .dt-shell .control-module .control-field { padding-left:38px; }
|
|
583
|
+
.devtools-ready .dt-shell .map-controls .control-field.dt-palette-field { grid-template-columns:70px minmax(0,1fr); align-items:center; }
|
|
584
|
+
.devtools-ready .dt-shell .dt-palettes { gap:4px; align-items:center; }
|
|
585
|
+
.devtools-ready .dt-shell .dt-palettes button { flex-direction:row; align-items:center; gap:5px; padding:2px 6px; min-width:0; height:var(--dt-control-height); font-size:11px; white-space:nowrap; }
|
|
586
|
+
.devtools-ready .dt-shell .dt-palette-swatches { width:24px; height:10px; flex:none; border-radius:2px; }
|
|
587
|
+
.devtools-ready .dt-shell .dt-palette-swatches i { min-width:0; }
|
|
588
|
+
@container(max-width:480px) {
|
|
589
|
+
.devtools-ready .dt-shell .map-controls .control-field { grid-template-columns:110px minmax(0,1fr); }
|
|
590
|
+
.devtools-ready .dt-shell .control-module .control-field { padding-left:24px; }
|
|
591
|
+
.devtools-ready .dt-shell .dt-camera-field input[type=range] { width:48px; }
|
|
592
|
+
}
|
|
593
|
+
`;
|
|
594
|
+
|
|
595
|
+
// src/devtools/styles.ts
|
|
596
|
+
var legacyStyles = `
|
|
597
|
+
body.devtools-ready { color-scheme:light dark; --paper:light-dark(#fff,#202124); --surface:light-dark(#f1f3f4,#292a2d); --ink:light-dark(#202124,#e8eaed); --muted:light-dark(#62676e,#9aa0a6); --border:light-dark(#dadce0,#3c4043); --accent:light-dark(#1a73e8,#8ab4f8); --dt-selected:light-dark(#e8f0fe,#394457); --dt-string:light-dark(#a31515,#e6b98b); --dt-number:light-dark(#1a1aa6,#a8c7fa); font:12px/1.45 Arial,system-ui,sans-serif; background:var(--paper); }
|
|
598
|
+
.devtools-ready button,.devtools-ready input,.devtools-ready select,.devtools-ready summary { min-height:24px; border-radius:2px; font:inherit; }
|
|
599
|
+
.devtools-ready button { padding:3px 8px; background:var(--paper); color:var(--ink); }
|
|
600
|
+
.devtools-ready button:hover { background:var(--surface); border-color:var(--border); }
|
|
601
|
+
.devtools-ready button:focus-visible,.devtools-ready input:focus-visible,.devtools-ready select:focus-visible { outline:2px solid var(--accent); outline-offset:-2px; }
|
|
602
|
+
.devtools-ready button[aria-pressed=true] { color:var(--accent); background:var(--dt-selected); }
|
|
603
|
+
.devtools-ready .tool-icon { width:14px; height:14px; }
|
|
604
|
+
.devtools-ready .workspace { display:grid; grid-template-columns:minmax(0,1fr); grid-template-rows:minmax(180px,1fr) var(--dt-height,340px); flex:1; min-height:0; }
|
|
605
|
+
.devtools-ready .workspace[data-dock=right] { grid-template-columns:minmax(200px,1fr) max(640px,var(--dt-width,760px)); grid-template-rows:minmax(0,1fr); }
|
|
606
|
+
.devtools-ready .workspace[data-dock=left] { grid-template-columns:max(640px,var(--dt-width,760px)) minmax(200px,1fr); grid-template-rows:minmax(0,1fr); }
|
|
607
|
+
.devtools-ready .workspace[data-dock=left] > .stage { grid-column:2; grid-row:1; }
|
|
608
|
+
.devtools-ready .workspace[data-dock=left] > .dt-shell { grid-column:1; grid-row:1; border-right:1px solid var(--border); }
|
|
609
|
+
.devtools-ready .workspace[data-collapsed=true] { grid-template-columns:minmax(0,1fr); grid-template-rows:minmax(0,1fr); }
|
|
610
|
+
.devtools-ready .workspace[data-collapsed=true] > .dt-shell { display:none; }
|
|
611
|
+
.devtools-ready .workspace > .inspector { display:none !important; }
|
|
612
|
+
.devtools-ready .inspect-hint { top:8px; font-size:11px; padding:4px 7px; background:var(--paper); color:var(--ink); border:1px solid var(--accent); }
|
|
613
|
+
#dt-reopen { display:none; position:absolute; top:8px; left:8px; z-index:10; box-shadow:0 1px 4px #0002; }
|
|
614
|
+
.workspace[data-collapsed=true] > #dt-reopen { display:block; }
|
|
615
|
+
.devtools-ready .workspace[data-collapsed=true] > .stage { grid-column:1; grid-row:1; }
|
|
616
|
+
.dt-shell { container-type:inline-size; display:flex; flex-direction:column; min-width:0; min-height:0; overflow:visible; position:relative; background:var(--paper); border-top:1px solid var(--border); color:var(--ink); }
|
|
617
|
+
.dt-head { min-height:40px; display:flex; align-items:center; border-bottom:1px solid var(--border); background:var(--surface); padding:0 3px; position:relative; flex:none; }
|
|
618
|
+
.dt-head-main { display:flex; align-items:center; gap:12px; flex:1; min-width:0; align-self:stretch; }
|
|
619
|
+
.dt-tabs { display:flex; min-width:0; overflow-x:auto; scrollbar-width:none; flex:0 1 auto; align-self:stretch; }
|
|
620
|
+
.dt-tabs button { border:0; border-bottom:2px solid transparent; background:transparent; border-radius:0; white-space:nowrap; padding:8px 10px; min-height:38px; }
|
|
621
|
+
.dt-tabs button[aria-selected=true] { color:var(--accent); border-bottom-color:var(--accent); background:var(--paper); }
|
|
622
|
+
.dt-head .dt-icon,.dt-tools .dt-icon { display:inline-flex; align-items:center; justify-content:center; border:0; background:transparent; padding:3px; width:26px; height:26px; flex:none; }
|
|
623
|
+
.dt-head .dt-icon:disabled { opacity:.35; cursor:default; }
|
|
624
|
+
.dt-icon svg { width:16px; height:16px; pointer-events:none; }
|
|
625
|
+
.dt-divider { height:16px; border-left:1px solid var(--border); margin:0 4px; }
|
|
626
|
+
.dt-camera { display:flex; align-items:center; flex-wrap:nowrap; gap:8px; min-width:0; flex:none; border:0; border-left:1px solid var(--border); margin:5px 0; padding:0 0 0 12px; overflow-x:auto; scrollbar-width:thin; }
|
|
627
|
+
.dt-camera-field { display:flex; align-items:center; gap:4px; min-width:0; flex:none; }
|
|
628
|
+
.dt-camera-field > label { color:var(--muted); font-size:11px; line-height:1; }
|
|
629
|
+
.dt-stepper { display:flex; align-items:stretch; height:26px; border:1px solid var(--border); border-radius:4px; overflow:hidden; background:var(--paper); }
|
|
630
|
+
.dt-stepper input[type=number] { appearance:textfield; -moz-appearance:textfield; width:54px; min-width:0; min-height:0; padding:2px 0; border:0; border-radius:0; background:transparent; color:var(--ink); text-align:center; font-variant-numeric:tabular-nums; }
|
|
631
|
+
.dt-stepper input::-webkit-inner-spin-button,.dt-stepper input::-webkit-outer-spin-button { -webkit-appearance:none; margin:0; }
|
|
632
|
+
.dt-stepper button { width:24px; min-height:0; padding:0; border:0; border-radius:0; background:transparent; font-size:15px; line-height:1; }
|
|
633
|
+
.dt-stepper button:disabled { color:var(--muted); opacity:.4; cursor:default; }
|
|
634
|
+
.dt-camera:disabled { opacity:.5; }
|
|
635
|
+
.dt-camera-reset { flex:none; height:26px; }
|
|
636
|
+
.dt-menu { position:absolute; z-index:40; right:5px; top:40px; padding:8px; background:var(--paper); color:var(--ink); border:1px solid var(--border); box-shadow:0 4px 16px #0003; min-width:190px; border-radius:4px; }
|
|
637
|
+
:is([data-dock=left],[data-dock=right]) .dt-head { flex-wrap:wrap; }
|
|
638
|
+
:is([data-dock=left],[data-dock=right]) .dt-head > .dt-icon { margin-block:6px; }
|
|
639
|
+
:is([data-dock=left],[data-dock=right]) .dt-head-main { display:contents; }
|
|
640
|
+
:is([data-dock=left],[data-dock=right]) .dt-tabs { flex:1; }
|
|
641
|
+
:is([data-dock=left],[data-dock=right]) .dt-camera-field { flex:1 0 96px; }
|
|
642
|
+
:is([data-dock=left],[data-dock=right]) .dt-stepper { flex:1; min-width:0; }
|
|
643
|
+
:is([data-dock=left],[data-dock=right]) .dt-stepper input[type=number] { flex:1; width:0; }
|
|
644
|
+
:is([data-dock=left],[data-dock=right]) .dt-stepper button { flex:none; width:22px; }
|
|
645
|
+
:is([data-dock=left],[data-dock=right]) .dt-menu { top:38px; }
|
|
646
|
+
@container(max-width:1050px) {
|
|
647
|
+
.dt-head { flex-wrap:wrap; }
|
|
648
|
+
.dt-head > .dt-icon { margin-block:6px; }
|
|
649
|
+
.dt-head-main { display:contents; }
|
|
650
|
+
.dt-tabs { flex:1; }
|
|
651
|
+
.dt-camera-field { flex:1 0 96px; }
|
|
652
|
+
.dt-stepper { flex:1; min-width:0; }
|
|
653
|
+
.dt-stepper input[type=number] { flex:1; width:0; }
|
|
654
|
+
.dt-stepper button { flex:none; width:22px; }
|
|
655
|
+
.dt-menu { top:38px; }
|
|
656
|
+
}
|
|
657
|
+
.dt-menu-label { color:var(--muted); margin:2px 0 6px; }
|
|
658
|
+
.dt-dock-options { display:flex; gap:4px; }
|
|
659
|
+
.dt-dock-options button { padding:6px; display:flex; }
|
|
660
|
+
.dt-dock-options svg { width:24px; height:20px; }
|
|
661
|
+
.dt-resize { position:absolute; z-index:5; height:6px; left:0; right:0; top:-4px; cursor:ns-resize; touch-action:none; }
|
|
662
|
+
[data-dock=right] > .dt-shell > .dt-resize { top:0; bottom:0; left:-4px; width:6px; height:auto; right:auto; cursor:ew-resize; }
|
|
663
|
+
[data-dock=left] > .dt-shell > .dt-resize { top:0; bottom:0; right:-4px; width:6px; height:auto; left:auto; cursor:ew-resize; }
|
|
664
|
+
.dt-resize-shield { position:absolute; inset:0; z-index:30; background:transparent; }
|
|
665
|
+
.dt-body { flex:1; display:flex; flex-direction:column; min-height:0; overflow:hidden; }
|
|
666
|
+
.dt-panel { flex:1; min-height:0; min-width:0; display:flex; flex-direction:column; overflow:hidden; }
|
|
667
|
+
.dt-tools { min-height:29px; display:flex; flex-wrap:wrap; align-items:center; gap:5px; border-bottom:1px solid var(--border); padding:3px 6px; flex:none; }
|
|
668
|
+
.dt-tools input[type=search] { width:160px; flex:1; min-width:75px; border:1px solid var(--border); background:var(--paper); color:var(--ink); padding:2px 6px; }
|
|
669
|
+
.dt-tools label { display:flex; align-items:center; gap:4px; white-space:nowrap; color:var(--muted); }
|
|
670
|
+
.dt-tools select { max-width:160px; padding:2px; }
|
|
671
|
+
.dt-tools input[type=checkbox] { min-height:0; margin:0; accent-color:var(--accent); }
|
|
672
|
+
.dt-tools button { border-color:transparent; background:transparent; }
|
|
673
|
+
.dt-footer { min-height:23px; padding:3px 8px; border-top:1px solid var(--border); color:var(--muted); font-size:11px; flex:none; display:flex; gap:12px; }
|
|
674
|
+
.dt-scroll { flex:1; overflow:auto; min-height:0; min-width:0; }
|
|
675
|
+
.dt-empty { color:var(--muted); padding:22px; text-align:center; margin:auto; max-width:420px; }
|
|
676
|
+
.dt-empty strong { color:var(--ink); display:block; margin-bottom:6px; font-weight:400; }
|
|
677
|
+
.dt-inspect-grid { display:grid; grid-template-columns:minmax(220px,1fr) minmax(300px,42%); flex:1; min-height:0; }
|
|
678
|
+
.dt-tree-column { display:flex; flex-direction:column; min-height:0; border-right:1px solid var(--border); }
|
|
679
|
+
.dt-tree { flex:1; min-height:0; overflow:auto; font:12px/1.65 ui-monospace,SFMono-Regular,Consolas,monospace; padding:5px 0; }
|
|
680
|
+
.dt-tree-row { display:flex; width:100%; gap:6px; align-items:center; min-height:23px; padding:2px 8px; white-space:nowrap; border:0 !important; border-radius:0 !important; text-align:left; background:transparent !important; font:inherit !important; }
|
|
681
|
+
.dt-tree-row[aria-selected=true] { background:var(--dt-selected) !important; }
|
|
682
|
+
.dt-tree-row:hover { background:var(--surface) !important; }
|
|
683
|
+
.dt-tree-row .dt-token { color:var(--dt-number); }
|
|
684
|
+
.dt-tree-row .dt-attr { color:var(--dt-string); }
|
|
685
|
+
.dt-tree-row .dt-dim { color:var(--muted); }
|
|
686
|
+
.dt-tree-row .dt-truncate { overflow:hidden; text-overflow:ellipsis; }
|
|
687
|
+
.dt-details { min-width:0; min-height:0; display:flex; flex-direction:column; }
|
|
688
|
+
.dt-subtabs { display:flex; gap:2px; border-bottom:1px solid var(--border); flex:none; overflow-x:auto; }
|
|
689
|
+
.dt-subtabs button { border:0; border-bottom:2px solid transparent; border-radius:0; padding:4px 8px; white-space:nowrap; }
|
|
690
|
+
.dt-subtabs button[aria-selected=true] { border-bottom-color:var(--accent); color:var(--accent); }
|
|
691
|
+
.dt-rule { border-bottom:1px solid var(--border); padding:7px 9px; font:12px/1.7 ui-monospace,SFMono-Regular,Consolas,monospace; }
|
|
692
|
+
.dt-rule-title { display:flex; justify-content:space-between; gap:8px; color:var(--ink); overflow-wrap:anywhere; margin-bottom:4px; }
|
|
693
|
+
.dt-rule-origin { color:var(--muted); font:11px/1.6 Arial,sans-serif; text-align:right; }
|
|
694
|
+
.dt-property { display:flex; align-items:center; gap:4px; padding:1px 0 1px 14px; min-height:24px; }
|
|
695
|
+
.dt-property-name { color:light-dark(#881391,#c58af9); flex:none; }
|
|
696
|
+
.dt-property input[type=text],.dt-property input[type=number],.dt-property select { border:1px solid transparent; background:transparent; min-width:30px; flex:1; width:100%; padding:0 2px; color:var(--dt-string); border-radius:0; font:inherit; min-height:22px; }
|
|
697
|
+
.dt-property input:focus,.dt-property input:hover { border-color:var(--border); }
|
|
698
|
+
.dt-property input[type=color] { width:14px; height:14px; padding:0; min-height:0; border:0; flex:none; background:transparent; cursor:pointer; }
|
|
699
|
+
.dt-property input[type=color]::-webkit-color-swatch-wrapper { padding:0; }
|
|
700
|
+
.dt-property input[type=color]::-webkit-color-swatch { border:1px solid var(--border); }
|
|
701
|
+
.dt-property .dt-revert { border:0; padding:0 3px; min-height:18px; color:var(--muted); background:transparent; }
|
|
702
|
+
.dt-property[data-modified=true] { background:color-mix(in srgb,var(--accent) 8%,transparent); }
|
|
703
|
+
.dt-expression { display:block; padding-left:14px; color:var(--muted); white-space:pre-wrap; overflow-wrap:anywhere; font-size:11px; }
|
|
704
|
+
.dt-table { border-collapse:collapse; width:100%; font-size:11px; text-align:left; }
|
|
705
|
+
.dt-table th { position:sticky; top:0; z-index:1; background:var(--surface); font-weight:400; border-bottom:1px solid var(--border); border-right:1px solid var(--border); padding:3px 7px; white-space:nowrap; }
|
|
706
|
+
.dt-table th button { font:inherit; border:0; padding:0; min-height:18px; background:transparent; }
|
|
707
|
+
.dt-table td { padding:3px 7px; border-bottom:1px solid color-mix(in srgb,var(--border) 45%,transparent); max-width:320px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; height:24px; }
|
|
708
|
+
.dt-table tbody tr:nth-child(even) { background:color-mix(in srgb,var(--surface) 55%,transparent); }
|
|
709
|
+
.dt-table tbody tr[aria-selected=true] { background:var(--dt-selected); }
|
|
710
|
+
.dt-table tbody tr:hover { background:var(--surface); }
|
|
711
|
+
.dt-table .dt-cell-button { text-align:left; border:0; background:transparent; min-height:18px; padding:0; color:inherit; max-width:100%; overflow:hidden; text-overflow:ellipsis; font:inherit; }
|
|
712
|
+
.dt-table .dt-number { font-variant-numeric:tabular-nums; text-align:right; }
|
|
713
|
+
.dt-table .dt-error { color:light-dark(#b3261e,#f2b8b5); }
|
|
714
|
+
.dt-network-body { display:flex; flex:1; min-height:0; min-width:0; }
|
|
715
|
+
.dt-network-body > .dt-scroll { flex:1; min-width:0; }
|
|
716
|
+
.dt-network-body[data-selected=true] > .dt-scroll { flex:1 1 55%; }
|
|
717
|
+
.dt-network-detail { display:flex; flex-direction:column; flex:1 1 45%; min-width:290px; min-height:0; border-left:1px solid var(--border); background:var(--paper); }
|
|
718
|
+
.dt-network-detail[hidden] { display:none; }
|
|
719
|
+
.dt-tile-row { cursor:pointer; }
|
|
720
|
+
.dt-tile-heading { flex:none; justify-content:space-between; }
|
|
721
|
+
.dt-tile-heading strong { font-size:12px; font-weight:500; }
|
|
722
|
+
.dt-tile-heading button { border:0; font-size:18px; }
|
|
723
|
+
.dt-tile-content { min-height:0; }
|
|
724
|
+
.dt-tile-content select { width:100%; min-width:0; background:var(--paper); color:var(--ink); border:1px solid var(--border); padding:4px; }
|
|
725
|
+
.dt-tile-paging { justify-content:space-between; }
|
|
726
|
+
.dt-tile-feature { border-bottom:1px solid var(--border); padding:5px 8px; }
|
|
727
|
+
.dt-tile-feature summary { cursor:pointer; overflow-wrap:anywhere; font-size:11px; }
|
|
728
|
+
.dt-tile-feature details { padding-left:12px; }
|
|
729
|
+
.dt-tile-feature pre { white-space:pre-wrap; overflow-wrap:anywhere; font:11px/1.6 ui-monospace,monospace; color:var(--dt-string); margin:6px 0; }
|
|
730
|
+
.dt-kv { display:grid; grid-template-columns:minmax(100px,auto) minmax(0,1fr); gap:5px 12px; font:12px/1.5 ui-monospace,monospace; padding:8px; margin:0; }
|
|
731
|
+
.dt-kv dt { color:var(--muted); overflow-wrap:anywhere; }.dt-kv dd { margin:0; overflow-wrap:anywhere; }
|
|
732
|
+
.dt-note { color:var(--muted); font-size:11px; padding:6px 8px; margin:0; }
|
|
733
|
+
.dt-status-message { color:var(--accent); padding:4px 8px; border-top:1px solid var(--border); font-size:11px; }
|
|
734
|
+
.dt-shell .notice { position:static; padding:5px 9px; border:0; font:11px/1.5 ui-monospace,monospace; box-shadow:none; color:light-dark(#b3261e,#f2b8b5); }
|
|
735
|
+
.dt-scene-content { display:flex; flex-direction:column; flex:1; min-height:0; min-width:0; width:100%; max-width:1000px; align-self:flex-start; }
|
|
736
|
+
.dt-shell .map-controls { flex:1; width:100%; max-width:none; padding:0 16px 16px; border:0; overflow:auto; background:var(--paper); }
|
|
737
|
+
.dt-shell .controls-grid,.dt-shell #controls-modules { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:0 24px; align-items:start; }
|
|
738
|
+
.dt-shell .controls-grid { border-bottom:1px solid var(--border); padding-bottom:8px; }
|
|
739
|
+
.dt-shell .controls-selection,.dt-shell .controls-runtime { border:0; padding-bottom:0; }
|
|
740
|
+
.dt-shell .map-controls .control-field,.dt-shell .control-module-header label { display:grid; grid-template-columns:min(36%,160px) minmax(0,1fr); align-items:center; gap:12px; min-height:28px; }
|
|
741
|
+
.dt-shell .map-controls .control-field > select,.dt-shell .map-controls .control-field > input[type=number] { justify-self:start; width:min(200px,100%); max-width:100%; }
|
|
742
|
+
.dt-shell .map-controls input[type=checkbox] { appearance:none; -webkit-appearance:none; position:relative; justify-self:start; flex:none; width:34px; height:20px; min-height:20px; margin:0; padding:0; border:1px solid light-dark(#9aa0a6,#7c838d); border-radius:999px; background:light-dark(#e3e7ed,#444950); cursor:pointer; transition:background-color 120ms ease,border-color 120ms ease; }
|
|
743
|
+
.dt-shell .map-controls input[type=checkbox]::before { content:''; position:absolute; top:2px; left:2px; width:14px; height:14px; border-radius:50%; background:light-dark(#fff,#c4c8cf); box-shadow:0 1px 2px #0003; transition:transform 120ms ease,background-color 120ms ease; }
|
|
744
|
+
.dt-shell .map-controls input[type=checkbox]:checked { background:var(--accent); border-color:var(--accent); }
|
|
745
|
+
.dt-shell .map-controls input[type=checkbox]:checked::before { transform:translateX(14px); background:light-dark(#fff,#202124); }
|
|
746
|
+
.dt-shell .map-controls input[type=checkbox]:hover:not(:disabled) { box-shadow:0 0 0 3px color-mix(in srgb,var(--accent) 12%,transparent); }
|
|
747
|
+
.dt-shell .map-controls input[type=checkbox]:focus-visible { outline:2px solid var(--accent); outline-offset:3px; }
|
|
748
|
+
.dt-shell .map-controls input[type=checkbox]:disabled { cursor:not-allowed; }
|
|
749
|
+
.dt-shell .map-controls .control-field:has(input[type=checkbox]:not(:disabled)) { cursor:pointer; }
|
|
750
|
+
.dt-shell .controls-heading button { min-width:60px; }
|
|
751
|
+
.dt-shell .controls-segments button { padding:4px 10px; border-radius:0; }
|
|
752
|
+
@media (prefers-reduced-motion:reduce) { .dt-shell .map-controls input[type=checkbox],.dt-shell .map-controls input[type=checkbox]::before { transition:none; } }
|
|
753
|
+
.dt-shell .control-module { border:0; border-bottom:1px solid var(--border); border-radius:0; background:var(--paper); margin:0; }
|
|
754
|
+
.dt-shell .control-module-header { padding:5px 0; }.dt-shell .control-module-header label { font-size:12px; font-weight:400; }
|
|
755
|
+
.dt-shell .control-module fieldset { min-width:0; padding:0 0 8px 16px; }
|
|
756
|
+
.dt-shell .controls-section-title { margin:10px 0 4px; font-size:11px; letter-spacing:0; text-transform:none; }
|
|
757
|
+
.dt-shell .control-field { margin:5px 0; }
|
|
758
|
+
@container(max-width:680px) { .dt-shell .controls-grid,.dt-shell #controls-modules { grid-template-columns:minmax(0,1fr); }.dt-shell .map-controls .control-field,.dt-shell .control-module-header label { grid-template-columns:min(36%,160px) minmax(0,1fr); }.dt-shell .controls-segments button { padding-inline:8px; } }
|
|
759
|
+
.dt-shell .controls-code { background:var(--surface); }.dt-shell .controls-selection .control-field select { max-width:100%; }
|
|
760
|
+
.dt-shell .panel-heading h2 { font:12px Arial,sans-serif; }.dt-shell .review-tabs { margin:0; padding:0; }
|
|
761
|
+
.dt-assets-content { scrollbar-gutter:stable; }
|
|
762
|
+
.dt-font-list { display:grid; grid-template-columns:repeat(auto-fit,minmax(min(100%,350px),1fr)); max-width:1100px; }
|
|
763
|
+
.dt-font-card { min-width:0; margin:0; padding:14px 12px; border-bottom:1px solid var(--border); }
|
|
764
|
+
.dt-font-card figcaption { margin-bottom:8px; font:12px/1.5 ui-monospace,monospace; }
|
|
765
|
+
.dt-font-sample { min-height:94px; overflow-x:auto; border:1px solid var(--border); border-radius:3px; background:#fff; color:#303b4b; }
|
|
766
|
+
.dt-font-preview { display:block; }
|
|
767
|
+
.dt-font-loading { display:block; padding:15px 10px; font-size:11px; color:#62676e; }
|
|
768
|
+
.dt-sprite-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(112px,1fr)); gap:1px; padding:1px; background:var(--border); }
|
|
769
|
+
.dt-sprite-card { margin:0; min-width:0; padding:10px 8px 8px; background:var(--paper); text-align:center; }
|
|
770
|
+
.dt-sprite-card:hover { background:var(--dt-selected); }
|
|
771
|
+
.dt-sprite-preview { height:60px; display:flex; align-items:center; justify-content:center; border:1px solid #dadce0; border-radius:3px; background:repeating-conic-gradient(#e8eaed 0% 25%,#fff 0% 50%) 0 0 / 12px 12px; color:#62676e; }
|
|
772
|
+
.dt-sprite-card figcaption { margin-top:7px; font:11px/1.4 ui-monospace,monospace; overflow-wrap:anywhere; }
|
|
773
|
+
.dt-sprite-size { display:block; min-height:15px; margin-top:3px; color:var(--muted); font-size:10px; }
|
|
774
|
+
.dt-geometry-json { width:100%; min-height:100px; resize:vertical; background:var(--paper); color:var(--ink); border:1px solid var(--border); font:11px/1.5 ui-monospace,monospace; padding:6px; }
|
|
775
|
+
.dt-vertex { position:absolute; width:10px; height:10px; border:2px solid #1a73e8; background:#fff; transform:translate(-50%,-50%); pointer-events:auto; padding:0; min-height:0; border-radius:1px; cursor:move; touch-action:none; }
|
|
776
|
+
.dt-geometry-handles { position:absolute; inset:0; pointer-events:none; z-index:5; overflow:hidden; }
|
|
777
|
+
.dt-performance { padding:10px; }.dt-performance svg { width:100%; height:90px; display:block; }.dt-performance dl { max-width:600px; }
|
|
778
|
+
.dt-overdraw-help { max-width:640px; margin:10px 10px 0; padding:10px 12px; border:1px solid var(--border); border-left:3px solid var(--accent); border-radius:4px; background:var(--surface); }
|
|
779
|
+
.dt-overdraw-help[hidden] { display:none; }
|
|
780
|
+
.dt-overdraw-help strong { font-size:12px; }.dt-overdraw-help p { margin:5px 0 0; line-height:1.5; }
|
|
781
|
+
.dt-overdraw-legend { display:flex; flex-wrap:wrap; gap:6px 20px; margin:10px 0 8px; }
|
|
782
|
+
.dt-overdraw-key { display:inline-flex; align-items:center; gap:6px; font:11px/1.4 ui-monospace,monospace; }
|
|
783
|
+
.dt-overdraw-swatch { width:24px; height:10px; border:1px solid #0002; border-radius:2px; }
|
|
784
|
+
.dt-overdraw-caption { color:var(--muted); font-size:11px; }
|
|
785
|
+
.dt-badge { background:var(--dt-selected); color:var(--accent); border-radius:7px; padding:0 5px; margin-left:3px; font:10px/1.5 Arial,sans-serif; }
|
|
786
|
+
[data-dock=left] .dt-inspect-grid,[data-dock=right] .dt-inspect-grid { display:flex; flex-direction:column; }
|
|
787
|
+
[data-dock=left] .dt-tree-column,[data-dock=right] .dt-tree-column { flex:0 0 32%; border-right:0; border-bottom:1px solid var(--border); }
|
|
788
|
+
[data-dock=left] .dt-details,[data-dock=right] .dt-details { flex:1; }
|
|
789
|
+
.dt-zoom { padding:8px; }.dt-zoom > select { width:100%; margin-bottom:8px; }
|
|
790
|
+
@container(max-width:680px) { .dt-inspect-grid { display:flex;flex-direction:column; }.dt-tree-column { flex:0 0 32%;border-right:0;border-bottom:1px solid var(--border); }.dt-details { flex:1; } }
|
|
791
|
+
`;
|
|
792
|
+
var devtoolsStyles = legacyStyles + inspectorStyles + cardStyles;
|
|
793
|
+
|
|
794
|
+
// src/devtools/theme-labels.ts
|
|
795
|
+
var themeLabels = {
|
|
796
|
+
light: "Light",
|
|
797
|
+
default: "Default",
|
|
798
|
+
faded: "Faded",
|
|
799
|
+
monochrome: "Monochrome"
|
|
800
|
+
};
|
|
801
|
+
var defaultPaletteMapIds = ["streets", "map-1777"];
|
|
802
|
+
|
|
803
|
+
// src/workbench-client.ts
|
|
804
|
+
var workbenchClient = String.raw`
|
|
805
|
+
let wbCatalog;
|
|
806
|
+
let wbLastContext = "";
|
|
807
|
+
let wbInterval;
|
|
808
|
+
let wbRefreshing = false;
|
|
809
|
+
let wbError = "";
|
|
810
|
+
let wbLastPublished = 0;
|
|
811
|
+
let wbPickerKey = "";
|
|
812
|
+
let wbTimeOfDay = "";
|
|
813
|
+
const wbPaletteColors = new Map();
|
|
814
|
+
const wbThemeLabels = ${JSON.stringify(themeLabels)};
|
|
815
|
+
const wbDefaultPaletteMaps = ${JSON.stringify(defaultPaletteMapIds)};
|
|
816
|
+
const wbThemeLabel = (theme, metadata) => theme === "light" && wbDefaultPaletteMaps.some(id => metadata?.["tileflow:map"] === id || (Array.isArray(metadata?.["tileflow:extends"]) && metadata["tileflow:extends"].includes(id))) ? "Default" : Object.hasOwn(wbThemeLabels,theme) ? wbThemeLabels[theme] : theme.replace(/[-_]/g," ");
|
|
817
|
+
const wbClient = "view-" + Math.random().toString(36).slice(2);
|
|
818
|
+
const wb = (id) => document.getElementById("wb-" + id);
|
|
819
|
+
if (options.workbenchUrl) initializeWorkbench();
|
|
820
|
+
|
|
821
|
+
function initializeWorkbench() {
|
|
822
|
+
wb("theme").addEventListener("change", () => selectWorkbenchAppearance());
|
|
823
|
+
for (const [moment, label] of Object.entries({day:"Day", dawn:"Dawn", dusk:"Dusk", night:"Night"})) {
|
|
824
|
+
const segment = document.createElement("button");
|
|
825
|
+
segment.type = "button"; segment.value = moment; segment.textContent = label;
|
|
826
|
+
segment.setAttribute("role", "radio"); segment.setAttribute("aria-checked", "false");
|
|
827
|
+
segment.tabIndex = -1; segment.disabled = true;
|
|
828
|
+
segment.addEventListener("click", () => selectWorkbenchAppearance(moment));
|
|
829
|
+
segment.addEventListener("keydown", (event) => {
|
|
830
|
+
const direction = {ArrowRight:1, ArrowDown:1, ArrowLeft:-1, ArrowUp:-1}[event.key];
|
|
831
|
+
if (!direction && event.key !== "Home" && event.key !== "End") return;
|
|
832
|
+
event.preventDefault();
|
|
833
|
+
const enabled = [...wb("time-of-day").querySelectorAll("button")].filter((item) => !item.disabled);
|
|
834
|
+
if (!enabled.length) return;
|
|
835
|
+
const index = event.key === "Home" ? 0 : event.key === "End" ? enabled.length - 1 : (enabled.indexOf(segment) + direction + enabled.length) % enabled.length;
|
|
836
|
+
const next = enabled[index];
|
|
837
|
+
selectWorkbenchAppearance(next.value); next.focus();
|
|
838
|
+
});
|
|
839
|
+
wb("time-of-day").append(segment);
|
|
840
|
+
}
|
|
841
|
+
void refreshWorkbench();
|
|
842
|
+
wbInterval = setInterval(refreshWorkbench, 2500);
|
|
843
|
+
addEventListener("beforeunload", () => clearInterval(wbInterval));
|
|
844
|
+
addEventListener("keydown", (event) => {
|
|
845
|
+
if (event.target.closest?.("input,select,textarea,[contenteditable]")) return;
|
|
846
|
+
if (event.key === "i" && !event.metaKey && !event.ctrlKey) setInspectionEnabled(!inspectionEnabled, true);
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
async function workbenchRequest(path, body) {
|
|
851
|
+
const response = await fetch(options.workbenchUrl + path, body === undefined
|
|
852
|
+
? {cache:"no-store"}
|
|
853
|
+
: {method:"POST", headers:{"Content-Type":"application/json", "X-Tileflow-Workbench":"1"}, body:JSON.stringify(body)});
|
|
854
|
+
const result = await response.json();
|
|
855
|
+
if (!response.ok) throw new Error(result.message || "Map DevTools request failed.");
|
|
856
|
+
return result;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
async function refreshWorkbench() {
|
|
860
|
+
if (wbRefreshing) return;
|
|
861
|
+
wbRefreshing = true;
|
|
862
|
+
try {
|
|
863
|
+
wbCatalog = await workbenchRequest("");
|
|
864
|
+
const side = states[activeSideId];
|
|
865
|
+
const owner = wbCatalog.sides[side.serviceId || side.id];
|
|
866
|
+
resolveWorkbenchAppearance(side, owner);
|
|
867
|
+
if (side.bridgeConnected && side.workbenchMap && side.workbenchTheme && !side.workbenchRestored) {
|
|
868
|
+
side.workbenchRestored = true;
|
|
869
|
+
if (side.id === "left") {
|
|
870
|
+
const query = new URL(location.href).searchParams;
|
|
871
|
+
const requested = owner?.selections.find((entry) => !entry.scene && entry.map === side.workbenchMap && entry.id === query.get("view"));
|
|
872
|
+
if (requested) {
|
|
873
|
+
side.requestedTimeOfDay = query.get("time-of-day");
|
|
874
|
+
applyWorkbenchAppearance(side, requested);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
syncWorkbenchPickers();
|
|
879
|
+
const failure = side.runtimeFailed ? side.runtimeErrors.at(-1) : owner?.status === "invalid" ? owner.diagnostics.map((item) => item.message).join(" ") : wbError;
|
|
880
|
+
wb("notice").hidden = !failure;
|
|
881
|
+
wb("notice").textContent = failure ? (owner?.status === "invalid" ? "Showing the last available preview. " : "") + failure : "";
|
|
882
|
+
await publishWorkbenchContext();
|
|
883
|
+
} catch (error) { showWorkbenchError(error); }
|
|
884
|
+
finally { wbRefreshing = false; }
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
function resolveWorkbenchAppearance(side, owner) {
|
|
888
|
+
const source = new URL(side.definition.previewUrl, location.href);
|
|
889
|
+
const startup = owner?.selections.find((entry) => entry.scene && entry.scene === source.searchParams.get("scene"));
|
|
890
|
+
const metadata = side.bridgeConnected ? side.map?.getStyle?.()?.metadata : undefined;
|
|
891
|
+
// The command chooses the map. Never infer it from catalog order or a saved URL's view.
|
|
892
|
+
side.workbenchMap ||= source.searchParams.get("map") || startup?.map || metadata?.["tileflow:map"];
|
|
893
|
+
if (!side.pendingAppearance && metadata?.["tileflow:map"] === side.workbenchMap) side.workbenchTheme = metadata?.["tileflow:theme"] || side.workbenchTheme;
|
|
894
|
+
side.workbenchTheme ||= source.searchParams.get("theme") || startup?.theme;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
function applyWorkbenchAppearance(side, selected) {
|
|
898
|
+
if (selected.map !== side.workbenchMap || selected.scene) return;
|
|
899
|
+
const changed = selected.theme !== side.workbenchTheme;
|
|
900
|
+
side.workbenchTheme = selected.theme;
|
|
901
|
+
if (changed || side.pendingAppearance) {
|
|
902
|
+
side.pendingAppearance = selected;
|
|
903
|
+
side.definition.label = selected.label;
|
|
904
|
+
scheduleMapControls();
|
|
905
|
+
void loadMapControls();
|
|
906
|
+
}
|
|
907
|
+
syncWorkbenchPickers();
|
|
908
|
+
writeUrlState();
|
|
909
|
+
if (side.id === "left") {
|
|
910
|
+
const url = new URL(location.href);
|
|
911
|
+
url.searchParams.set("view", selected.id);
|
|
912
|
+
const moment = wbTimeOfDay;
|
|
913
|
+
if (moment) url.searchParams.set("time-of-day", moment); else url.searchParams.delete("time-of-day");
|
|
914
|
+
history.replaceState(null, "", url);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
function syncWorkbenchPickers() {
|
|
919
|
+
const side = states[activeSideId];
|
|
920
|
+
const owner = wbCatalog?.sides[side.serviceId || side.id];
|
|
921
|
+
const selections = owner?.selections.filter((entry) => entry.map === side.workbenchMap) || [];
|
|
922
|
+
const selectedTheme = side.workbenchTheme;
|
|
923
|
+
if (!side.camera || !selections.length || !selectedTheme) {
|
|
924
|
+
wbPickerKey = "";
|
|
925
|
+
wb("theme").replaceChildren(new Option("Waiting for map…", ""));
|
|
926
|
+
wb("theme").disabled = true;
|
|
927
|
+
wb("time-of-day-field").hidden = true;
|
|
928
|
+
wbTimeOfDay = "";
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
const variants = owner?.timeOfDay?.[side.workbenchMap] || {};
|
|
932
|
+
const themeMetadata = side.map?.getStyle?.()?.metadata;
|
|
933
|
+
const key = JSON.stringify([activeSideId, selectedTheme, selections, variants, side.requestedTimeOfDay, themeMetadata?.["tileflow:map"], themeMetadata?.["tileflow:extends"]]);
|
|
934
|
+
if (key === wbPickerKey) return;
|
|
935
|
+
wbPickerKey = key;
|
|
936
|
+
const allThemes = [...new Set(selections.map((entry) => entry.theme))];
|
|
937
|
+
const family = Object.keys(variants).find((name) => Object.values(variants[name]).includes(selectedTheme));
|
|
938
|
+
const members = new Set(Object.values(variants).flatMap((moments) => Object.values(moments)));
|
|
939
|
+
const themes = allThemes.filter((name) => !members.has(name) || Object.hasOwn(variants, name));
|
|
940
|
+
if (themeMetadata?.["tileflow:map"] === "streets" || (Array.isArray(themeMetadata?.["tileflow:extends"]) && themeMetadata["tileflow:extends"].includes("streets"))) {
|
|
941
|
+
const streetsOrder = new Map([["light", 0], ["faded", 1], ["monochrome", 2]]);
|
|
942
|
+
themes.sort((left, right) => (streetsOrder.get(left) ?? 3) - (streetsOrder.get(right) ?? 3));
|
|
943
|
+
}
|
|
944
|
+
const moments = family ? variants[family] : {};
|
|
945
|
+
wbTimeOfDay = moments[side.requestedTimeOfDay] === selectedTheme ? side.requestedTimeOfDay : Object.keys(moments).find((moment) => moments[moment] === selectedTheme) || "";
|
|
946
|
+
for (const segment of wb("time-of-day").querySelectorAll("button")) {
|
|
947
|
+
const selected = segment.value === wbTimeOfDay;
|
|
948
|
+
segment.setAttribute("aria-checked", String(selected));
|
|
949
|
+
segment.tabIndex = selected ? 0 : -1;
|
|
950
|
+
segment.disabled = !moments[segment.value];
|
|
951
|
+
}
|
|
952
|
+
wb("time-of-day-field").hidden = !family;
|
|
953
|
+
wb("theme").replaceChildren(...themes.map((theme) => new Option(wbThemeLabel(theme,themeMetadata), theme)));
|
|
954
|
+
wb("theme").value = family || selectedTheme;
|
|
955
|
+
wb("theme").disabled = themes.length < 2;
|
|
956
|
+
wb("palettes").replaceChildren(...themes.map(theme => {
|
|
957
|
+
const button=document.createElement("button");button.type="button";button.setAttribute("role","radio");button.setAttribute("aria-checked",String(theme===(family||selectedTheme)));
|
|
958
|
+
const swatches=document.createElement("span");swatches.className="dt-palette-swatches";
|
|
959
|
+
const label=document.createElement("span");label.textContent=wbThemeLabel(theme,themeMetadata);button.append(swatches,label);
|
|
960
|
+
button.onclick=()=>{wb("theme").value=theme;selectWorkbenchAppearance();};
|
|
961
|
+
const concrete=variants[theme]?.[wbTimeOfDay] || variants[theme]?.day || theme;
|
|
962
|
+
const fill=colors=>{if(!button.isConnected)return;swatches.replaceChildren(...colors.map(color=>{const swatch=document.createElement("i");swatch.style.background=color;return swatch;}));};
|
|
963
|
+
if(wbPaletteColors.has(concrete))queueMicrotask(()=>fill(wbPaletteColors.get(concrete)));
|
|
964
|
+
else {
|
|
965
|
+
const url=new URL(side.definition.basePath+"/styles/"+side.workbenchMap+"/"+concrete+".json",location.href);
|
|
966
|
+
fetch(url).then(response=>{if(!response.ok)throw new Error("Theme unavailable");return response.json();}).then(style=>{
|
|
967
|
+
const choices=[/background/,/water/,/land.*(?:park|vegetation)|vegetation/,/building/];
|
|
968
|
+
const colors=choices.map(pattern=>style.layers.flatMap(layer=>pattern.test(layer.id)||pattern.test(layer.type)?Object.entries(layer.paint||{}).filter(([key,value])=>key.endsWith("-color")&&typeof value==="string").map(([,value])=>value):[])[0]).filter(Boolean);
|
|
969
|
+
wbPaletteColors.set(concrete,colors);fill(colors);
|
|
970
|
+
}).catch(()=>{swatches.textContent="—";});
|
|
971
|
+
}
|
|
972
|
+
return button;
|
|
973
|
+
}));
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
function selectWorkbenchAppearance(requested = wbTimeOfDay) {
|
|
977
|
+
const side = states[activeSideId];
|
|
978
|
+
const owner = wbCatalog?.sides[side.serviceId || side.id];
|
|
979
|
+
const moments = owner?.timeOfDay?.[side.workbenchMap]?.[wb("theme").value];
|
|
980
|
+
const moment = moments?.[requested] ? requested : moments ? Object.keys(moments)[0] : undefined;
|
|
981
|
+
const theme = moments?.[moment] || wb("theme").value;
|
|
982
|
+
const selected = owner?.selections.find((entry) => !entry.scene && entry.map === side.workbenchMap && entry.theme === theme);
|
|
983
|
+
if (!selected) { wbPickerKey = ""; syncWorkbenchPickers(); return; }
|
|
984
|
+
side.requestedTimeOfDay = moment;
|
|
985
|
+
applyWorkbenchAppearance(side, selected);
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
function currentWorkbenchDefinition() {
|
|
989
|
+
const side = states[activeSideId];
|
|
990
|
+
const camera = side.camera || canonicalCamera;
|
|
991
|
+
const metadata = side.map?.getStyle?.().metadata;
|
|
992
|
+
if (!camera || !validPortableName(metadata?.["tileflow:map"]) || !validConcreteThemeName(metadata?.["tileflow:theme"]))
|
|
993
|
+
throw new Error("Wait for the current map to finish loading.");
|
|
994
|
+
const container = side.map?.getContainer?.();
|
|
995
|
+
const width = boundedInteger(container?.clientWidth, 64, 4096, 1280);
|
|
996
|
+
const height = boundedInteger(container?.clientHeight, 64, 4096, 800);
|
|
997
|
+
return {map:metadata["tileflow:map"], theme:metadata["tileflow:theme"],
|
|
998
|
+
camera:{type:"center", center:[roundNumber(camera.center[0]),roundNumber(camera.center[1])], zoom:roundNumber(camera.zoom), bearing:roundNumber(camera.bearing), pitch:roundNumber(camera.pitch)},
|
|
999
|
+
viewport:{width,height,dpr:1}, target:{kind:"map"}};
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
function workbenchContext() {
|
|
1003
|
+
const side = states[activeSideId];
|
|
1004
|
+
const layerObservation = globalThis.__tileflowDevtoolsUI?.layerContext?.();
|
|
1005
|
+
const selected = !layerObservation && selectedInspection?.side === side ? selectedInspection : undefined;
|
|
1006
|
+
const feature = selected?.features[selected.featureIndex];
|
|
1007
|
+
const style = side.map?.getStyle?.();
|
|
1008
|
+
const metadata = style?.metadata;
|
|
1009
|
+
const layer = feature && style?.layers?.find((entry) => entry.id === feature.layerId);
|
|
1010
|
+
const provenance = feature && findSidecarLayer(side.sidecar, feature.layerId);
|
|
1011
|
+
let definition;
|
|
1012
|
+
try { definition = currentWorkbenchDefinition(); } catch { /* Loading previews have no camera yet. */ }
|
|
1013
|
+
const themeVariants = wbCatalog?.sides[side.serviceId || side.id]?.timeOfDay?.[definition?.map] || {};
|
|
1014
|
+
const themeFamily = definition && Object.keys(themeVariants).find(name => Object.values(themeVariants[name]).includes(definition.theme));
|
|
1015
|
+
const appearance = definition ? {theme:wbThemeLabel(themeFamily || definition.theme,metadata), themeId:themeFamily || definition.theme,
|
|
1016
|
+
timeOfDay:themeFamily ? (themeVariants[themeFamily][wbTimeOfDay] === definition.theme ? wbTimeOfDay : Object.keys(themeVariants[themeFamily]).find(moment => themeVariants[themeFamily][moment] === definition.theme)) : undefined,
|
|
1017
|
+
concreteThemeId:definition.theme} : undefined;
|
|
1018
|
+
const configuration = side.definition.captureConfig ? " --config " + quoteCliArgument(side.definition.captureConfig) : "";
|
|
1019
|
+
const semanticOwner = provenance && sidecarLayerWithSemanticAttribution(provenance).semanticAttribution[0]?.semanticOwner;
|
|
1020
|
+
const captureCommand = definition ? "tileflow capture" + configuration + " --map " + quoteCliArgument(definition.map) + " --theme " + quoteCliArgument(definition.theme) +
|
|
1021
|
+
" --center=" + definition.camera.center.join(",") + " --zoom=" + definition.camera.zoom + " --bearing=" + definition.camera.bearing + " --pitch=" + definition.camera.pitch +
|
|
1022
|
+
" --width=" + definition.viewport.width + " --height=" + definition.viewport.height + " --dpr=" + definition.viewport.dpr + " --json" : undefined;
|
|
1023
|
+
return {schemaVersion:1, source:"browser-observation", config:side.definition.captureConfig || "tileflow.config.ts",
|
|
1024
|
+
status:side.artifactStatus, generation:side.artifactGeneration, inspectionEnabled,
|
|
1025
|
+
displayedGeneration:Number(side.frame.contentDocument?.documentElement.dataset.tileflowArtifactGeneration), definition, appearance,
|
|
1026
|
+
captureSemantics:"Compiled map at the observed camera and viewport; temporary preview edits are excluded.",
|
|
1027
|
+
review:{referenceId:null, before:null, after:null, liveMode:stage.dataset.mode, source:"map"},
|
|
1028
|
+
commands:{inspect:"tileflow inspect" + configuration + (semanticOwner ? " --domain " + quoteCliArgument(semanticOwner) : " --limit 80") + " --json", capture:captureCommand,
|
|
1029
|
+
session:"tileflow inspect session --url " + quoteCliArgument(location.origin) + " --json"},
|
|
1030
|
+
selection:layerObservation || (feature ? {coordinate:selected.coordinate,
|
|
1031
|
+
provenance:provenance ? sidecarLayerWithSemanticAttribution(provenance).semanticAttribution : [],
|
|
1032
|
+
provenanceScope:"Semantic ownership only; exact file, line and token are not inferred.",
|
|
1033
|
+
feature, layer:layer ? {id:layer.id,type:layer.type,source:layer.source,sourceLayer:layer["source-layer"],minzoom:layer.minzoom,maxzoom:layer.maxzoom,filter:layer.filter,paint:layer.paint,layout:layer.layout} : undefined} : null)};
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
function boundedWorkbenchContext() {
|
|
1037
|
+
const observed = workbenchContext();
|
|
1038
|
+
const nodeLimit = observed.selection?.kind === "layer" ? 2400 : 200;
|
|
1039
|
+
let entries = 0;
|
|
1040
|
+
const omitted = [];
|
|
1041
|
+
function visit(value, path, depth) {
|
|
1042
|
+
if (++entries > nodeLimit || depth > 16) { omitted.push(path); return undefined; }
|
|
1043
|
+
if (typeof value === "string") { if (value.length > 512) omitted.push(path); return value.slice(0,512); }
|
|
1044
|
+
if (!value || typeof value !== "object") return value;
|
|
1045
|
+
if (Array.isArray(value)) {
|
|
1046
|
+
if (value.length > 32) omitted.push(path);
|
|
1047
|
+
return value.slice(0,32).map((entry,index) => visit(entry,path + "/" + index,depth + 1));
|
|
1048
|
+
}
|
|
1049
|
+
return Object.fromEntries(Object.entries(value).flatMap(([key,entry]) => {
|
|
1050
|
+
const result = visit(entry,path + "/" + key.replaceAll("~","~0").replaceAll("/","~1"),depth + 1);
|
|
1051
|
+
return result === undefined ? [] : [[key,result]];
|
|
1052
|
+
}));
|
|
1053
|
+
}
|
|
1054
|
+
const context = visit(observed,"",0);
|
|
1055
|
+
context.projection = {truncated:omitted.length > 0, omitted:omitted.slice(0,32)};
|
|
1056
|
+
return context;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
async function publishWorkbenchContext() {
|
|
1060
|
+
const side = states[activeSideId];
|
|
1061
|
+
if (!side.bridgeReady) return;
|
|
1062
|
+
const context = boundedWorkbenchContext();
|
|
1063
|
+
const key = JSON.stringify(context);
|
|
1064
|
+
if (key === wbLastContext && Date.now() - wbLastPublished < 60_000) return;
|
|
1065
|
+
await workbenchRequest("/context", {client:wbClient, side:side.serviceId || side.id, generation:side.artifactGeneration, context});
|
|
1066
|
+
wbLastContext = key;
|
|
1067
|
+
wbLastPublished = Date.now();
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
function featureDisplayName(feature) {
|
|
1071
|
+
return String(feature.properties?.name || feature.properties?.class || feature.layerId.replace(/^tileflow-/, "").replace(/-render-/, " · ") || "Map element")
|
|
1072
|
+
.replace(/([a-z])([A-Z])/g, "$1 $2").replaceAll("_", " ").replaceAll("-", " ").replace(/^./, (letter) => letter.toUpperCase());
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
function renderFeatureSummary(side, feature, layer, picker, raw) {
|
|
1076
|
+
const section = document.createElement("section"); section.className = "feature-summary";
|
|
1077
|
+
const heading = document.createElement("h3");
|
|
1078
|
+
heading.textContent = featureDisplayName(feature);
|
|
1079
|
+
const kind = document.createElement("div"); kind.className = "compact-note";
|
|
1080
|
+
const kinds = {fill:"Area", line:"Line", symbol:"Label or icon", "fill-extrusion":"3D building", circle:"Point", raster:"Imagery", hillshade:"Terrain shading", background:"Background"};
|
|
1081
|
+
kind.textContent = (kinds[layer?.type] || layer?.type || feature.geometryType) + " · Zoom " + (side.camera?.zoom ?? 0).toFixed(2);
|
|
1082
|
+
const headingStyle = document.createElement("h4"); headingStyle.textContent = "Appearance here";
|
|
1083
|
+
section.append(heading, kind, headingStyle);
|
|
1084
|
+
const values = document.createElement("dl"); values.className = "property-list";
|
|
1085
|
+
const labels = {"fill-color":"Fill color", "fill-opacity":"Opacity", "fill-outline-color":"Outline", "line-color":"Line color", "line-width":"Width", "line-opacity":"Opacity", "line-gap-width":"Gap", "text-color":"Text color", "text-halo-color":"Text outline", "text-halo-width":"Outline width", "text-opacity":"Text opacity", "text-size":"Text size", "icon-color":"Icon color", "icon-opacity":"Icon opacity", "fill-extrusion-color":"Building color", "fill-extrusion-height":"Height", "fill-extrusion-opacity":"Opacity"};
|
|
1086
|
+
for (const [name, rawValue] of Object.entries({...layer?.paint, ...(layer?.layout?.["text-size"] !== undefined ? {"text-size":layer.layout["text-size"]} : {})}).slice(0,8)) {
|
|
1087
|
+
const value = evaluateBasicZoomValue(rawValue, side.camera?.zoom ?? 0);
|
|
1088
|
+
const label = document.createElement("dt"); label.textContent = labels[name] || name.replaceAll("-", " "); label.title = name;
|
|
1089
|
+
const detail = document.createElement("dd"); detail.textContent = value === unsupportedValue ? "Varies by feature" : formatCurveValue(value);
|
|
1090
|
+
if (typeof value === "string" && /^(#[0-9a-f]{3,8}|rgba?\(|hsla?\()/i.test(value)) {
|
|
1091
|
+
const swatch = document.createElement("span"); swatch.className = "color-swatch"; swatch.style.backgroundColor = value; swatch.setAttribute("aria-hidden", "true"); detail.append(swatch);
|
|
1092
|
+
}
|
|
1093
|
+
values.append(label,detail);
|
|
1094
|
+
}
|
|
1095
|
+
section.append(values);
|
|
1096
|
+
const provenance = findSidecarLayer(side.sidecar, feature.layerId);
|
|
1097
|
+
const owners = provenance ? sidecarLayerWithSemanticAttribution(provenance).semanticAttribution : [];
|
|
1098
|
+
const label = document.createElement("h4"); label.textContent = "Where to edit";
|
|
1099
|
+
section.append(label);
|
|
1100
|
+
const paths = [...new Set(owners.flatMap((owner) => owner.authoringPaths))];
|
|
1101
|
+
for (const path of paths.slice(0,8)) { const code = document.createElement("code"); code.className = "authoring-path"; code.textContent = path; section.append(code); }
|
|
1102
|
+
const note = document.createElement("p"); note.className = "compact-note";
|
|
1103
|
+
note.textContent = paths.length ? "Edit these configuration sections in your editor or ask your coding agent. The map updates here." : "No semantic configuration path is available for this layer.";
|
|
1104
|
+
section.append(note);
|
|
1105
|
+
const details = document.createElement("details"); const summary = document.createElement("summary"); summary.textContent = "Feature data";
|
|
1106
|
+
const provenanceNote = document.createElement("p"); provenanceNote.className = "compact-note"; provenanceNote.textContent = "Configuration paths describe semantic ownership. Exact source lines and tokens are not reported.";
|
|
1107
|
+
details.append(summary,provenanceNote,raw);
|
|
1108
|
+
inspectionOutput.append(section,picker,details);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
function showWorkbenchError(error) {
|
|
1112
|
+
const message = safeErrorMessage(error);
|
|
1113
|
+
wbError = message;
|
|
1114
|
+
setFeedback(message);
|
|
1115
|
+
wb("notice").textContent = message; wb("notice").hidden = false;
|
|
1116
|
+
}
|
|
1117
|
+
`;
|
|
1118
|
+
|
|
1119
|
+
// src/workbench-controls.ts
|
|
1120
|
+
var controlsStyles = `
|
|
1121
|
+
.map-controls { flex:0 0 360px; width:360px; max-width:45vw; min-height:0; overflow:auto; border-left:1px solid var(--border); background:var(--paper); padding:0 18px 18px; }
|
|
1122
|
+
.map-controls[hidden] { display:none; }
|
|
1123
|
+
.controls-heading { display:flex; align-items:center; justify-content:space-between; gap:12px; padding:8px 0; border-bottom:1px solid var(--border); }
|
|
1124
|
+
.controls-heading h2 { margin:0; font:inherit; }
|
|
1125
|
+
.controls-segments { display:flex; min-width:0; width:max-content; max-width:100%; border:1px solid var(--border); border-radius:4px; overflow:hidden; }
|
|
1126
|
+
.controls-segments button { flex:1; min-width:0; padding:4px 10px; border:0; border-radius:0; background:var(--paper); color:var(--muted); cursor:pointer; }
|
|
1127
|
+
.controls-segments button + button { border-left:1px solid var(--border); }
|
|
1128
|
+
.controls-segments button[aria-checked=true] { color:var(--accent); background:var(--dt-selected,var(--surface)); box-shadow:inset 0 -2px var(--accent); }
|
|
1129
|
+
.controls-segments button:disabled { opacity:.4; cursor:not-allowed; }
|
|
1130
|
+
.controls-selection { border-bottom:1px solid var(--border); padding-bottom:8px; }
|
|
1131
|
+
.controls-selection .control-field select { width:190px; max-width:70%; }
|
|
1132
|
+
.controls-selection [hidden] { display:none; }
|
|
1133
|
+
.controls-section-title { font-size:10px; letter-spacing:.1em; text-transform:uppercase; color:var(--muted); margin:20px 0 9px; }
|
|
1134
|
+
.control-module { border:1px solid var(--border); border-radius:8px; margin:7px 0; background:#fffdf7; overflow:hidden; }
|
|
1135
|
+
.control-module-header { display:flex; align-items:center; justify-content:space-between; padding:10px 12px; gap:8px; }
|
|
1136
|
+
.control-module-header label { display:flex; align-items:center; justify-content:space-between; flex:1; flex-direction:row; gap:10px; font-size:13px; font-weight:600; cursor:pointer; }
|
|
1137
|
+
.control-module-header input { appearance:none; width:30px; height:18px; min-height:0; padding:0; border:0; border-radius:12px; background:#d5cec1; cursor:pointer; position:relative; flex:none; }
|
|
1138
|
+
.control-module-header input::before { content:''; position:absolute; width:12px; height:12px; top:3px; left:3px; border-radius:50%; background:#fff; }
|
|
1139
|
+
.control-module-header input:checked { background:var(--accent); }
|
|
1140
|
+
.control-module-header input:checked::before { transform:translateX(12px); }
|
|
1141
|
+
.control-module-header input:focus-visible { outline:2px solid var(--accent); outline-offset:3px; }
|
|
1142
|
+
.control-module fieldset { border:0; padding:0 12px 12px; margin:0; }
|
|
1143
|
+
.control-module fieldset[hidden] { display:none; }
|
|
1144
|
+
.control-field { display:flex; align-items:center; justify-content:space-between; flex-direction:row; gap:12px; font-size:12px; margin:9px 0; }
|
|
1145
|
+
.control-field select,.control-field input[type=number] { width:132px; min-width:0; padding:5px 7px; font:inherit; }
|
|
1146
|
+
.control-field input[type=checkbox] { accent-color:var(--accent); }
|
|
1147
|
+
.controls-runtime { border:0; border-bottom:1px solid var(--border); padding:0 0 12px; margin:0; min-width:0; }
|
|
1148
|
+
#controls-modules { border:0; padding:0; margin:0; min-width:0; }
|
|
1149
|
+
.controls-runtime:disabled,#controls-modules:disabled { opacity:.6; }
|
|
1150
|
+
#controls-status { font-size:12px; line-height:1.5; color:var(--muted); margin:8px 0; overflow-wrap:anywhere; }
|
|
1151
|
+
#controls-status[data-error=true] { color:#a32e36; }
|
|
1152
|
+
@media(max-width:760px) { .workspace:has(.map-controls:not([hidden])) { display:flex; flex-direction:column; } .map-controls { flex:0 1 55vh; width:auto; max-width:none; border-left:0; border-top:1px solid var(--border); } .workspace:has(.map-controls:not([hidden])) .stage { flex:1 0 35vh; } }
|
|
1153
|
+
`;
|
|
1154
|
+
var controlsPanel = `
|
|
1155
|
+
<aside class="map-controls" id="map-controls" aria-label="Scene settings" tabindex="-1" hidden>
|
|
1156
|
+
<div class="controls-heading"><h2>Styles</h2><button id="controls-reset">Reset</button></div>
|
|
1157
|
+
<div class="controls-grid">
|
|
1158
|
+
<div class="controls-selection workbench-only dt-card" id="controls-selection" hidden>
|
|
1159
|
+
<div class="controls-section-title">Appearance</div>
|
|
1160
|
+
<div class="control-field" id="wb-time-of-day-field" hidden><span>Time of day</span><div class="controls-segments" id="wb-time-of-day" role="radiogroup" aria-label="Time of day"></div></div>
|
|
1161
|
+
<div class="control-field dt-palette-field" id="wb-theme-field"><span>Theme</span><select id="wb-theme" aria-label="Theme" disabled hidden><option value="">Waiting for map\u2026</option></select><div id="wb-palettes" class="dt-palettes" role="radiogroup" aria-label="Theme"></div></div>
|
|
1162
|
+
</div>
|
|
1163
|
+
<fieldset class="controls-runtime dt-card" id="controls-runtime">
|
|
1164
|
+
<div class="controls-section-title">Preview rendering</div>
|
|
1165
|
+
<label class="control-field">3D view<input id="controls-3d" type="checkbox" role="switch"></label>
|
|
1166
|
+
<label class="control-field">Building animation<select id="controls-animation" hidden><option value="0">Off</option><option value="800">Grow</option><option value="fade">Fade</option></select><span class="controls-segments" id="controls-animation-segments" role="radiogroup" aria-label="Building animation"></span></label>
|
|
1167
|
+
</fieldset>
|
|
1168
|
+
</div>
|
|
1169
|
+
<section class="dt-card controls-module-card">
|
|
1170
|
+
<div class="controls-section-title">Map modules</div>
|
|
1171
|
+
<fieldset id="controls-modules"></fieldset>
|
|
1172
|
+
</section>
|
|
1173
|
+
<p id="controls-status" role="status" hidden></p>
|
|
1174
|
+
</aside>
|
|
1175
|
+
`;
|
|
1176
|
+
var controlsClient = String.raw`
|
|
1177
|
+
const ctl = (id) => document.getElementById("controls-" + id);
|
|
1178
|
+
const controlsPanel = document.getElementById("map-controls");
|
|
1179
|
+
const controlsButton = document.getElementById("controls-toggle");
|
|
1180
|
+
let controlsKey = "", controlsSequence = 0, controlsModel, controlsDraft = {}, controlsBusy = false;
|
|
1181
|
+
let controlsApplyTimer, controlsRevision = 0, controlsPending = false, controlsResetPending = false;
|
|
1182
|
+
let controlsLoadingKey = "", controlsBindings = [], controlsLastError = "";
|
|
1183
|
+
let controlsHistoryCurrent, controlsInitial, controlsHistoryQuiet = 0;
|
|
1184
|
+
const controlsUndo = [], controlsRedo = [];
|
|
1185
|
+
ctl("selection").hidden = !options.workbenchUrl;
|
|
1186
|
+
controlsButton.addEventListener("click", () => toggleMapControls(controlsPanel.hidden, true));
|
|
1187
|
+
ctl("reset").addEventListener("click", () => {
|
|
1188
|
+
if (!controlsModel) return;
|
|
1189
|
+
controlsDraft = {};
|
|
1190
|
+
ctl("3d").checked = false; ctl("animation").value = "0";
|
|
1191
|
+
renderMapControls();
|
|
1192
|
+
scheduleMapControls(true);
|
|
1193
|
+
});
|
|
1194
|
+
for (const name of ["3d", "animation"]) ctl(name).addEventListener("change", () => { syncAnimationSegments(); scheduleMapControls(); });
|
|
1195
|
+
for (const [value,label] of [["0","Off"],["800","Grow"],["fade","Fade"]]) {
|
|
1196
|
+
const button=document.createElement("button"); button.type="button";button.value=value;button.textContent=label;button.setAttribute("role","radio");
|
|
1197
|
+
button.addEventListener("click",()=>{ctl("animation").value=value;ctl("animation").dispatchEvent(new Event("change"));});ctl("animation-segments").append(button);
|
|
1198
|
+
}
|
|
1199
|
+
function syncAnimationSegments(){for(const button of ctl("animation-segments").children)button.setAttribute("aria-checked",String(button.value===ctl("animation").value));}
|
|
1200
|
+
syncAnimationSegments();
|
|
1201
|
+
addEventListener("keydown", (event) => { if (event.key === "Escape" && !controlsPanel.hidden) toggleMapControls(false, true); });
|
|
1202
|
+
const controlsTimer = setInterval(() => { if (!controlsPanel.hidden) void loadMapControls(); }, 1000);
|
|
1203
|
+
addEventListener("beforeunload", () => { clearInterval(controlsTimer); cancelMapControlsApply(); });
|
|
1204
|
+
|
|
1205
|
+
function cancelMapControlsApply() {
|
|
1206
|
+
clearTimeout(controlsApplyTimer); controlsApplyTimer = undefined;
|
|
1207
|
+
controlsPending = false; controlsResetPending = false; controlsRevision++;
|
|
1208
|
+
}
|
|
1209
|
+
function setMapControlsDisabled(disabled) {
|
|
1210
|
+
for (const name of ["modules", "runtime", "reset"]) ctl(name).disabled = disabled;
|
|
1211
|
+
}
|
|
1212
|
+
function scheduleMapControls(reset = false) {
|
|
1213
|
+
clearTimeout(controlsApplyTimer);
|
|
1214
|
+
controlsRevision++; controlsPending = true; controlsResetPending = reset;
|
|
1215
|
+
controlsNotice(reset ? "Restoring the original map…" : "Updating preview…");
|
|
1216
|
+
controlsApplyTimer = setTimeout(() => {
|
|
1217
|
+
controlsApplyTimer = undefined;
|
|
1218
|
+
void applyMapControls();
|
|
1219
|
+
}, reset ? 0 : 300);
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
function toggleMapControls(open, focus = false) {
|
|
1223
|
+
if (globalThis.__tileflowDevtoolsUI) { globalThis.__tileflowDevtoolsUI.controls(open); return; }
|
|
1224
|
+
if (open) {
|
|
1225
|
+
setInspectionEnabled(false);
|
|
1226
|
+
}
|
|
1227
|
+
controlsPanel.hidden = !open;
|
|
1228
|
+
controlsButton.setAttribute("aria-expanded", String(open));
|
|
1229
|
+
for (const side of Object.values(states)) side.map?.resize?.();
|
|
1230
|
+
if (open) void loadMapControls();
|
|
1231
|
+
if (focus) (open ? controlsPanel : controlsButton).focus();
|
|
1232
|
+
}
|
|
1233
|
+
function controlsNotice(text, error = false) {
|
|
1234
|
+
ctl("status").hidden = !error;
|
|
1235
|
+
ctl("status").textContent = error ? text : "";
|
|
1236
|
+
ctl("status").dataset.error = String(error);
|
|
1237
|
+
}
|
|
1238
|
+
function controlsIdentity(side) {
|
|
1239
|
+
const meta = side.map?.getStyle?.()?.metadata;
|
|
1240
|
+
return meta ? {map:meta["tileflow:map"], theme:meta["tileflow:theme"]} : undefined;
|
|
1241
|
+
}
|
|
1242
|
+
function livePreviewUrl(side) {
|
|
1243
|
+
return new URL(side.frame.contentWindow.location.href, location.href);
|
|
1244
|
+
}
|
|
1245
|
+
async function loadMapControls() {
|
|
1246
|
+
const side = states[activeSideId], identity = controlsIdentity(side);
|
|
1247
|
+
if (!side.bridgeConnected || !identity?.map || !identity.theme) {
|
|
1248
|
+
setMapControlsDisabled(!side.bridgeConnected || !controlsModel);
|
|
1249
|
+
controlsNotice("Waiting for the map to finish loading…");
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
identity.theme = side.workbenchTheme || identity.theme;
|
|
1253
|
+
const key = JSON.stringify([side.id, side.definition.basePath, identity.map, side.loadEpoch]);
|
|
1254
|
+
const epoch = side.loadEpoch;
|
|
1255
|
+
const loadingKey = key + identity.theme;
|
|
1256
|
+
if (key === controlsKey && controlsModel?.theme === identity.theme) {
|
|
1257
|
+
setMapControlsDisabled(false);
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
if (loadingKey === controlsLoadingKey) return;
|
|
1261
|
+
const preserve = key === controlsKey && controlsModel;
|
|
1262
|
+
if (!preserve) {
|
|
1263
|
+
const pendingAppearance = side.pendingAppearance;
|
|
1264
|
+
cancelMapControlsApply();
|
|
1265
|
+
controlsPending = Boolean(pendingAppearance);
|
|
1266
|
+
controlsKey = key;
|
|
1267
|
+
controlsModel = undefined;
|
|
1268
|
+
ctl("modules").replaceChildren(); setMapControlsDisabled(true);
|
|
1269
|
+
}
|
|
1270
|
+
controlsLoadingKey = loadingKey;
|
|
1271
|
+
const sequence = ++controlsSequence;
|
|
1272
|
+
controlsNotice("Reading this map’s modules…");
|
|
1273
|
+
try {
|
|
1274
|
+
const url = new URL(side.definition.basePath + "/__controls", location.href);
|
|
1275
|
+
url.search = new URLSearchParams(identity).toString();
|
|
1276
|
+
const response = await fetch(url, {cache:"no-store"});
|
|
1277
|
+
const model = await response.json();
|
|
1278
|
+
if (!response.ok) throw new Error(model.error || "Controls are unavailable for this preview.");
|
|
1279
|
+
if (sequence !== controlsSequence || activeSideId !== side.id || key !== controlsKey || epoch !== side.loadEpoch || identity.theme !== (side.workbenchTheme || controlsIdentity(side)?.theme)) return;
|
|
1280
|
+
if (preserve) {
|
|
1281
|
+
// Keep the actual inputs and their handlers mounted, including focus/selection.
|
|
1282
|
+
for (const module of controlsModel.modules) {
|
|
1283
|
+
const updated = model.modules.find((item) => item.id === module.id);
|
|
1284
|
+
if (!updated) continue;
|
|
1285
|
+
module.enabled = updated.enabled;
|
|
1286
|
+
for (const field of module.fields) Object.assign(field, updated.fields.find((item) => item.path === field.path));
|
|
1287
|
+
}
|
|
1288
|
+
controlsModel.theme = model.theme;
|
|
1289
|
+
for (const sync of controlsBindings) sync();
|
|
1290
|
+
} else {
|
|
1291
|
+
controlsModel = {...model, side, key, epoch};
|
|
1292
|
+
const query = livePreviewUrl(side).searchParams;
|
|
1293
|
+
controlsDraft = JSON.parse(query.get("controls") || "{}");
|
|
1294
|
+
side.appliedSceneStyle ||= JSON.stringify([identity.map, controlsIdentity(side).theme, controlsDraft]);
|
|
1295
|
+
ctl("3d").checked = query.get("buildings3d") !== "off";
|
|
1296
|
+
ctl("animation").value = query.get("animation") === "fade" ? "fade" : Number(query.get("animation")) > 0 ? "800" : "0"; syncAnimationSegments();
|
|
1297
|
+
renderMapControls();
|
|
1298
|
+
controlsUndo.length = controlsRedo.length = 0;
|
|
1299
|
+
controlsHistoryCurrent = globalThis.__tileflowScene.snapshot();
|
|
1300
|
+
controlsInitial = structuredClone(controlsHistoryCurrent);
|
|
1301
|
+
}
|
|
1302
|
+
setMapControlsDisabled(false);
|
|
1303
|
+
controlsNotice(Object.keys(controlsDraft).length ? "Preview overrides applied." : "Using the map’s original settings.");
|
|
1304
|
+
} catch (error) {
|
|
1305
|
+
if (sequence === controlsSequence) { if (!preserve) controlsKey = ""; controlsNotice(safeErrorMessage(error), true); }
|
|
1306
|
+
} finally {
|
|
1307
|
+
if (sequence === controlsSequence) {
|
|
1308
|
+
controlsLoadingKey = "";
|
|
1309
|
+
if (controlsPending && controlsApplyTimer === undefined) void applyMapControls();
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
function controlValue(object, path) { return path.split(".").reduce((value, key) => value?.[key], object); }
|
|
1314
|
+
function changeControl(module, path, value, original) {
|
|
1315
|
+
const patch = controlsDraft[module] ||= {};
|
|
1316
|
+
const parts = path.split("."); const leaf = parts.pop();
|
|
1317
|
+
let parent = patch;
|
|
1318
|
+
for (const part of parts) parent = parent[part] ||= {};
|
|
1319
|
+
if (value === original) delete parent[leaf]; else parent[leaf] = value;
|
|
1320
|
+
for (const [key, item] of Object.entries(patch)) if (item && typeof item === "object" && !Object.keys(item).length) delete patch[key];
|
|
1321
|
+
if (!Object.keys(patch).length) delete controlsDraft[module];
|
|
1322
|
+
scheduleMapControls();
|
|
1323
|
+
}
|
|
1324
|
+
function renderMapControls() {
|
|
1325
|
+
ctl("modules").replaceChildren();
|
|
1326
|
+
controlsBindings = [];
|
|
1327
|
+
const names = {poi:"Points of interest", land:"Land", water:"Water", nautical:"Nautical", roads:"Roads", buildings:"Buildings", boundaries:"Boundaries", labels:"Labels", aeroways:"Airports", transit:"Transit", vegetation:"Vegetation", addresses:"Addresses", landforms:"Landforms"};
|
|
1328
|
+
const modules = [...controlsModel.modules].sort((a,b) => {
|
|
1329
|
+
const priority = {vegetation:0, buildings:1};
|
|
1330
|
+
return (priority[a.id] ?? 2) - (priority[b.id] ?? 2);
|
|
1331
|
+
});
|
|
1332
|
+
for (const module of modules) {
|
|
1333
|
+
const card = document.createElement("section"); card.className = "control-module";
|
|
1334
|
+
const row = document.createElement("div"); row.className = "control-module-header";
|
|
1335
|
+
const label = document.createElement("label"); label.textContent = names[module.id] || module.id;
|
|
1336
|
+
const toggle = document.createElement("input"); toggle.type = "checkbox"; toggle.checked = controlsDraft[module.id]?.enabled ?? module.enabled;
|
|
1337
|
+
toggle.setAttribute("role", "switch");
|
|
1338
|
+
toggle.setAttribute("aria-label", "Enable " + (names[module.id] || module.id));
|
|
1339
|
+
label.append(toggle); row.append(label); card.append(row);
|
|
1340
|
+
const fieldset = document.createElement("fieldset");
|
|
1341
|
+
const syncModuleEnabled = () => {
|
|
1342
|
+
fieldset.disabled = !toggle.checked;
|
|
1343
|
+
fieldset.hidden = !toggle.checked;
|
|
1344
|
+
};
|
|
1345
|
+
syncModuleEnabled();
|
|
1346
|
+
controlsBindings.push(() => {
|
|
1347
|
+
if (document.activeElement !== toggle) toggle.checked = controlsDraft[module.id]?.enabled ?? module.enabled;
|
|
1348
|
+
syncModuleEnabled();
|
|
1349
|
+
});
|
|
1350
|
+
toggle.addEventListener("change", () => { changeControl(module.id, "enabled", toggle.checked, module.enabled); syncModuleEnabled(); });
|
|
1351
|
+
if (module.fields.length) {
|
|
1352
|
+
card.append(fieldset);
|
|
1353
|
+
for (const field of module.fields) {
|
|
1354
|
+
const line = document.createElement("label"); line.className = "control-field";
|
|
1355
|
+
const title = document.createElement("span"); title.textContent = field.label;
|
|
1356
|
+
const input = document.createElement(field.choices ? "select" : "input");
|
|
1357
|
+
const override = controlValue(controlsDraft[module.id], field.path);
|
|
1358
|
+
const value = field.inherit && override === undefined ? "inherit" : override ?? field.value;
|
|
1359
|
+
const labels = {"3d":"Always 3D",auto:"Follow 2D / 3D view",flat:"Flat (2D)",none:"Hidden",style:"Map light",sun:"Scene sun",faceted:"Faceted",smooth:"Smooth",toon:"Toon"};
|
|
1360
|
+
if (field.choices) {
|
|
1361
|
+
const choices = field.choices.map((choice) => new Option(labels[choice] || choice, choice));
|
|
1362
|
+
if (field.inherit) choices.unshift(new Option("Auto · " + (labels[field.value] || field.value), "inherit"));
|
|
1363
|
+
input.replaceChildren(...choices); input.value = String(value);
|
|
1364
|
+
} else if (typeof field.value === "boolean") { input.type = "checkbox"; input.setAttribute("role", "switch"); input.checked = value; }
|
|
1365
|
+
else { input.type = "number"; input.required = true; input.min = String(field.min); input.max = String(field.max); input.step = "any"; input.value = String(value); }
|
|
1366
|
+
controlsBindings.push(() => {
|
|
1367
|
+
if (document.activeElement === input || !input.checkValidity()) return;
|
|
1368
|
+
const override = controlValue(controlsDraft[module.id], field.path);
|
|
1369
|
+
const value = field.inherit && override === undefined ? "inherit" : override ?? field.value;
|
|
1370
|
+
if (input.type === "checkbox") input.checked = value;
|
|
1371
|
+
else input.value = String(value);
|
|
1372
|
+
if (field.inherit) input.options[0].textContent = "Auto · " + (labels[field.value] || field.value);
|
|
1373
|
+
});
|
|
1374
|
+
input.addEventListener(input.type === "number" ? "input" : "change", () => {
|
|
1375
|
+
if (!input.checkValidity()) { cancelMapControlsApply(); controlsNotice("Enter a valid value to update the preview.", true); return; }
|
|
1376
|
+
const value = field.inherit && input.value === "inherit" ? undefined : input.type === "checkbox" ? input.checked : input.type === "number" ? Number(input.value) : input.value;
|
|
1377
|
+
changeControl(module.id, field.path, value, field.inherit ? undefined : field.value);
|
|
1378
|
+
});
|
|
1379
|
+
line.append(title, input); fieldset.append(line);
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
ctl("modules").append(card);
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
async function applyMapControls() {
|
|
1386
|
+
if (!controlsModel || controlsBusy || !controlsPending) return;
|
|
1387
|
+
const revision = controlsRevision, reset = controlsResetPending;
|
|
1388
|
+
const {side, map, key} = controlsModel;
|
|
1389
|
+
const theme = side.workbenchTheme || controlsModel.theme;
|
|
1390
|
+
const epoch = side.loadEpoch, source = side.frame.src;
|
|
1391
|
+
if (!side.bridgeConnected || side.id !== activeSideId || side.loadEpoch !== controlsModel.epoch) return;
|
|
1392
|
+
if (!reset && [...ctl("modules").querySelectorAll("input")].some((input) => !input.checkValidity())) {
|
|
1393
|
+
controlsPending = false;
|
|
1394
|
+
controlsNotice("Enter valid field values to update the preview.", true); return;
|
|
1395
|
+
}
|
|
1396
|
+
const draft = reset ? {} : structuredClone(controlsDraft);
|
|
1397
|
+
const next = {map, theme, controls:draft, buildings3d:reset || ctl("3d").checked, animation:reset ? 0 : ctl("animation").value === "fade" ? "fade" : Number(ctl("animation").value)};
|
|
1398
|
+
const styleKey = JSON.stringify([map, theme, draft]);
|
|
1399
|
+
const isCurrent = () => revision === controlsRevision && controlsKey === key && side.id === activeSideId && side.bridgeConnected && epoch === side.loadEpoch && source === side.frame.src;
|
|
1400
|
+
const before = controlsHistoryCurrent && {...controlsHistoryCurrent, style:side.map.getStyle()};
|
|
1401
|
+
controlsBusy = true; controlsPending = false; controlsLastError = "";
|
|
1402
|
+
controlsNotice("Compiling preview…");
|
|
1403
|
+
try {
|
|
1404
|
+
if (styleKey !== side.appliedSceneStyle) {
|
|
1405
|
+
const style = new URL(side.definition.basePath + "/styles/" + map + "/" + theme + ".json", location.href);
|
|
1406
|
+
if (Object.keys(draft).length) style.searchParams.set("controls", JSON.stringify(draft));
|
|
1407
|
+
const response = await fetch(style, {cache:"no-store"});
|
|
1408
|
+
const document = await response.json();
|
|
1409
|
+
if (!response.ok) throw new Error(document.error || "Unable to compile these settings.");
|
|
1410
|
+
next.style = document;
|
|
1411
|
+
}
|
|
1412
|
+
if (!isCurrent()) return;
|
|
1413
|
+
const apply = side.frame.contentWindow.__tileflowApplyScene;
|
|
1414
|
+
if (!apply) throw new Error("Reload DevTools once to enable live scene updates.");
|
|
1415
|
+
if (!await apply(JSON.stringify(next), isCurrent)) return;
|
|
1416
|
+
side.appliedSceneStyle = styleKey;
|
|
1417
|
+
const after = globalThis.__tileflowScene.snapshot();
|
|
1418
|
+
if (!controlsHistoryQuiet && before && JSON.stringify([before.theme,before.controls,before.buildings3d,before.animation]) !== JSON.stringify([after.theme,after.controls,after.buildings3d,after.animation])) {
|
|
1419
|
+
controlsUndo.push({before,after,time:performance.now()}); if(controlsUndo.length>100)controlsUndo.shift(); controlsRedo.length=0;
|
|
1420
|
+
}
|
|
1421
|
+
controlsHistoryCurrent=after;
|
|
1422
|
+
side.pendingAppearance = undefined;
|
|
1423
|
+
side.definition.previewUrl = livePreviewUrl(side).href;
|
|
1424
|
+
side.sidecarAbort?.abort(); side.sidecar = undefined; side.sidecarPromise = undefined;
|
|
1425
|
+
if (selectedInspection?.side === side) void loadSidecar(side);
|
|
1426
|
+
if (isCurrent()) controlsNotice("");
|
|
1427
|
+
} catch (error) {
|
|
1428
|
+
if (revision === controlsRevision && controlsKey === key) { controlsLastError = safeErrorMessage(error); controlsNotice(controlsLastError + " The previous map is still shown.", true); }
|
|
1429
|
+
} finally {
|
|
1430
|
+
controlsBusy = false;
|
|
1431
|
+
if (controlsPending && controlsApplyTimer === undefined) void applyMapControls();
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
globalThis.__tileflowScene = {
|
|
1435
|
+
get history() {return {undo:controlsUndo.at(-1)?.time || 0,redo:controlsRedo.at(-1)?.redoTime || 0,changed:controlsUndo.length>0||controlsRedo.length>0};},
|
|
1436
|
+
async undo() {const entry=controlsUndo.at(-1);if(!entry)return;await this.restore(entry.before);controlsUndo.pop();entry.redoTime=performance.now();controlsRedo.push(entry);},
|
|
1437
|
+
async redo() {const entry=controlsRedo.at(-1);if(!entry)return;await this.restore(entry.after);controlsRedo.pop();entry.time=performance.now();controlsUndo.push(entry);},
|
|
1438
|
+
async reset() {if(controlsInitial)await this.restore(controlsInitial);controlsUndo.length=controlsRedo.length=0;},
|
|
1439
|
+
audit(active) {controlsHistoryQuiet=Math.max(0,controlsHistoryQuiet+(active?1:-1));},
|
|
1440
|
+
variants() {
|
|
1441
|
+
const side = states[activeSideId], owner = wbCatalog?.sides[side.serviceId || side.id];
|
|
1442
|
+
const variants = owner?.timeOfDay?.[side.workbenchMap] || {};
|
|
1443
|
+
return (owner?.selections || []).filter(entry => entry.map === side.workbenchMap && !entry.scene).map(entry => {
|
|
1444
|
+
const family = Object.keys(variants).find(name => Object.values(variants[name]).includes(entry.theme));
|
|
1445
|
+
return {theme:entry.theme, palette:family || entry.theme, timeOfDay:family ? Object.keys(variants[family]).find(moment => variants[family][moment] === entry.theme) : undefined};
|
|
1446
|
+
});
|
|
1447
|
+
},
|
|
1448
|
+
snapshot() {
|
|
1449
|
+
const side = states[activeSideId];
|
|
1450
|
+
return {side:side.id, theme:side.workbenchTheme || controlsIdentity(side)?.theme, controls:structuredClone(controlsDraft), buildings3d:ctl("3d").checked, animation:ctl("animation").value, style:side.map?.getStyle()};
|
|
1451
|
+
},
|
|
1452
|
+
async style(theme, signal) {
|
|
1453
|
+
const side = states[activeSideId], identity = controlsIdentity(side);
|
|
1454
|
+
const url = new URL(side.definition.basePath + "/styles/" + identity.map + "/" + theme + ".json", location.href);
|
|
1455
|
+
if (Object.keys(controlsDraft).length) url.searchParams.set("controls", JSON.stringify(controlsDraft));
|
|
1456
|
+
const response = await fetch(url, {signal});
|
|
1457
|
+
if (!response.ok) throw new Error("Could not read theme " + theme);
|
|
1458
|
+
return response.json();
|
|
1459
|
+
},
|
|
1460
|
+
async apply(theme, signal) {
|
|
1461
|
+
if (signal?.aborted) throw new Error("Audit cancelled.");
|
|
1462
|
+
const side = states[activeSideId], owner = wbCatalog?.sides[side.serviceId || side.id];
|
|
1463
|
+
const entry = owner?.selections.find(item => item.map === side.workbenchMap && item.theme === theme && !item.scene);
|
|
1464
|
+
if (!entry) throw new Error("Theme is not available: " + theme);
|
|
1465
|
+
applyWorkbenchAppearance(side, entry);
|
|
1466
|
+
const started = performance.now();
|
|
1467
|
+
do {
|
|
1468
|
+
if (signal?.aborted) throw new Error("Audit cancelled.");
|
|
1469
|
+
if (states[activeSideId] !== side) throw new Error("The active map changed.");
|
|
1470
|
+
if (performance.now() - started > 30000) throw new Error("Theme update timed out.");
|
|
1471
|
+
await new Promise(resolve => setTimeout(resolve,40));
|
|
1472
|
+
} while (controlsBusy || controlsPending || controlsLoadingKey || controlsApplyTimer !== undefined);
|
|
1473
|
+
if (controlsLastError) throw new Error(controlsLastError);
|
|
1474
|
+
if (controlsIdentity(side)?.theme !== theme) throw new Error("Theme could not be applied.");
|
|
1475
|
+
},
|
|
1476
|
+
async restore(saved) {
|
|
1477
|
+
if (states[activeSideId].id !== saved.side) return;
|
|
1478
|
+
controlsHistoryQuiet++;
|
|
1479
|
+
try {
|
|
1480
|
+
controlsDraft = structuredClone(saved.controls);
|
|
1481
|
+
ctl("3d").checked = saved.buildings3d; ctl("animation").value = saved.animation;
|
|
1482
|
+
scheduleMapControls();
|
|
1483
|
+
await this.apply(saved.theme);
|
|
1484
|
+
const side = states[activeSideId];
|
|
1485
|
+
if (saved.style) await side.frame.contentWindow.__tileflowApplyScene(JSON.stringify({map:controlsIdentity(side).map,theme:saved.theme,controls:saved.controls,buildings3d:saved.buildings3d,animation:saved.animation === "fade" ? "fade" : Number(saved.animation),style:saved.style}), () => states[activeSideId] === side);
|
|
1486
|
+
renderMapControls();syncAnimationSegments();controlsHistoryCurrent=this.snapshot();
|
|
1487
|
+
} finally {controlsHistoryQuiet--;}
|
|
1488
|
+
}
|
|
1489
|
+
};
|
|
1490
|
+
`;
|
|
1491
|
+
|
|
1492
|
+
// src/workbench-ui.ts
|
|
1493
|
+
function workbenchIcon(name) {
|
|
1494
|
+
const paths = {
|
|
1495
|
+
inspect: '<path d="M9 3H4a1 1 0 0 0-1 1v5m12-6h5a1 1 0 0 1 1 1v5M3 15v5a1 1 0 0 0 1 1h5"/><path d="m11 11 10 4-5 1-1 5z"/>',
|
|
1496
|
+
settings: '<path d="M4 7h16M4 17h16"/><circle cx="9" cy="7" r="3"/><circle cx="15" cy="17" r="3"/>',
|
|
1497
|
+
close: '<path d="m6 6 12 12M6 18 18 6"/>'
|
|
1498
|
+
};
|
|
1499
|
+
return `<svg class="tool-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">${paths[name]}</svg>`;
|
|
1500
|
+
}
|
|
1501
|
+
var workbenchStyles = `
|
|
1502
|
+
body { display:flex; flex-direction:column; }
|
|
1503
|
+
.workspace { flex:1; min-height:0; }
|
|
1504
|
+
input[type=range] { accent-color:var(--accent); }
|
|
1505
|
+
button:disabled { opacity:.5; cursor:not-allowed; }
|
|
1506
|
+
select,input[type=text],input[type=number] { background:var(--paper); color:var(--ink); border:1px solid var(--border); padding:5px 8px; }
|
|
1507
|
+
summary { cursor:pointer; }
|
|
1508
|
+
.tool-icon { width:17px; height:17px; flex:none; vertical-align:middle; }
|
|
1509
|
+
button:has(.tool-icon) { display:inline-flex; align-items:center; justify-content:center; gap:7px; }
|
|
1510
|
+
.inspect-hint { position:absolute; z-index:4; top:8px; left:8px; pointer-events:none; }
|
|
1511
|
+
.compact-note { font-size:12px; color:var(--muted); line-height:1.5; }
|
|
1512
|
+
.property-list { display:grid; grid-template-columns:minmax(0,1fr) auto; gap:8px; }
|
|
1513
|
+
.property-list dd { margin:0; }
|
|
1514
|
+
.authoring-path { display:block; overflow-wrap:anywhere; }
|
|
1515
|
+
.color-swatch { width:16px; height:16px; border:1px solid var(--border); border-radius:2px; display:inline-block; }
|
|
1516
|
+
`;
|
|
1517
|
+
var workbenchPanels = `<div id="wb-notice" role="status" hidden></div>`;
|
|
39
1518
|
|
|
40
1519
|
// src/comparison-html.ts
|
|
41
1520
|
function renderTileflowComparisonHtml(options) {
|
|
42
1521
|
const documentOptions = serializeInlineJson(options);
|
|
1522
|
+
const fontBasePath = options.basePath.split("/").map((segment) => encodeURIComponent(decodeURIComponent(segment)).replaceAll("'", "%27")).join("/");
|
|
43
1523
|
const nonceAttribute = options.cspNonce ? ` nonce="${escapeHtml(options.cspNonce)}"` : "";
|
|
44
1524
|
return `<!doctype html>
|
|
45
1525
|
<html lang="en" data-tileflow-state="loading">
|
|
@@ -48,6 +1528,10 @@ function renderTileflowComparisonHtml(options) {
|
|
|
48
1528
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
49
1529
|
<title>${escapeHtml(options.title)}</title>
|
|
50
1530
|
<style${nonceAttribute}>
|
|
1531
|
+
@font-face { font-family:'Inter'; font-weight:100 900; font-display:swap; src:url('${fontBasePath}/__workbench-fonts/inter.woff2') format('woff2'); }
|
|
1532
|
+
@font-face { font-family:'JetBrains Mono'; font-weight:400; font-display:swap; src:url('${fontBasePath}/__workbench-fonts/jetbrains-mono.woff2') format('woff2'); }
|
|
1533
|
+
@font-face { font-family:'Mona Sans'; font-weight:200 900; font-display:swap; src:url('${fontBasePath}/__workbench-fonts/mona-sans.woff2') format('woff2'); }
|
|
1534
|
+
@font-face { font-family:'Hubot Sans'; font-weight:200 900; font-display:swap; src:url('${fontBasePath}/__workbench-fonts/hubot-sans.woff2') format('woff2'); }
|
|
51
1535
|
:root {
|
|
52
1536
|
color-scheme: light;
|
|
53
1537
|
--accent: #3157d5;
|
|
@@ -59,6 +1543,8 @@ function renderTileflowComparisonHtml(options) {
|
|
|
59
1543
|
--overlay-alpha: .5;
|
|
60
1544
|
}
|
|
61
1545
|
* { box-sizing: border-box; }
|
|
1546
|
+
[hidden] { display: none !important; }
|
|
1547
|
+
.stage[data-mode="single"] .pane-right { display: none; }
|
|
62
1548
|
html, body { height: 100%; margin: 0; }
|
|
63
1549
|
body {
|
|
64
1550
|
display: grid;
|
|
@@ -83,49 +1569,10 @@ function renderTileflowComparisonHtml(options) {
|
|
|
83
1569
|
outline: 3px solid color-mix(in srgb, var(--accent) 45%, transparent);
|
|
84
1570
|
outline-offset: 2px;
|
|
85
1571
|
}
|
|
86
|
-
.toolbar {
|
|
87
|
-
z-index: 10;
|
|
88
|
-
display: flex;
|
|
89
|
-
min-height: 48px;
|
|
90
|
-
align-items: center;
|
|
91
|
-
gap: 8px;
|
|
92
|
-
padding: 7px 10px;
|
|
93
|
-
border-bottom: 1px solid var(--border);
|
|
94
|
-
background: rgba(255, 255, 255, .97);
|
|
95
|
-
flex-wrap: wrap;
|
|
96
|
-
}
|
|
97
|
-
.toolbar-title { font-weight: 750; margin-right: 4px; }
|
|
98
|
-
.toolbar-group { display: inline-flex; min-width: 0; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
99
|
-
.toolbar-separator { width: 1px; height: 25px; flex: 0 0 auto; background: var(--border); }
|
|
100
|
-
.toolbar label { display: inline-flex; align-items: center; gap: 5px; color: var(--muted); }
|
|
101
|
-
.toolbar input[type="range"] { width: 96px; }
|
|
102
|
-
.toolbar input[type="text"] { width: 150px; padding: 5px 7px; border: 1px solid var(--border); border-radius: 6px; }
|
|
103
1572
|
.workspace { position: relative; min-height: 0; display: grid; grid-template-columns: minmax(0, 1fr) auto; }
|
|
104
1573
|
.stage { position: relative; min-width: 0; min-height: 0; overflow: hidden; background: #d8dadd; }
|
|
105
1574
|
.pane { position: absolute; inset: 0; min-width: 0; min-height: 0; overflow: hidden; background: #d8dadd; }
|
|
106
1575
|
.pane iframe { width: 100%; height: 100%; display: block; border: 0; background: #d8dadd; }
|
|
107
|
-
.pane-label {
|
|
108
|
-
position: absolute;
|
|
109
|
-
z-index: 3;
|
|
110
|
-
top: 10px;
|
|
111
|
-
left: 10px;
|
|
112
|
-
display: flex;
|
|
113
|
-
max-width: calc(100% - 20px);
|
|
114
|
-
align-items: center;
|
|
115
|
-
gap: 7px;
|
|
116
|
-
border: 1px solid rgba(0, 0, 0, .12);
|
|
117
|
-
border-radius: 999px;
|
|
118
|
-
background: rgba(255, 255, 255, .93);
|
|
119
|
-
box-shadow: 0 3px 14px rgba(0, 0, 0, .12);
|
|
120
|
-
padding: 5px 9px;
|
|
121
|
-
pointer-events: none;
|
|
122
|
-
}
|
|
123
|
-
.pane-label-text { overflow: hidden; text-overflow: ellipsis; }
|
|
124
|
-
.side-state-text { flex: 0 0 auto; color: var(--muted); font-size: 10px; text-transform: uppercase; letter-spacing: .03em; }
|
|
125
|
-
.side-state { width: 8px; height: 8px; flex: 0 0 auto; border-radius: 50%; background: #8d929b; }
|
|
126
|
-
.side-state[data-status="ready"] { background: #248a4a; }
|
|
127
|
-
.side-state[data-status="building"] { background: #cf8114; }
|
|
128
|
-
.side-state[data-status="invalid"], .side-state[data-status="error"] { background: #ba3838; }
|
|
129
1576
|
.stage[data-mode="side-by-side"] { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 1px; }
|
|
130
1577
|
.stage[data-mode="side-by-side"] .pane { position: relative; inset: auto; }
|
|
131
1578
|
.stage[data-mode="side-by-side"] .pane-right { grid-column: 2; }
|
|
@@ -165,74 +1612,46 @@ function renderTileflowComparisonHtml(options) {
|
|
|
165
1612
|
.sprite-card { min-width: 0; border: 1px solid var(--border); border-radius: 7px; padding: 7px; background: var(--paper); text-align: center; }
|
|
166
1613
|
.sprite-card canvas { display: block; margin: auto; max-width: 64px; max-height: 64px; image-rendering: auto; }
|
|
167
1614
|
.sprite-name { display: block; margin-top: 4px; overflow: hidden; text-overflow: ellipsis; font-size: 10px; }
|
|
168
|
-
.feedback { color: var(--muted); min-width: 64px; }
|
|
169
1615
|
.visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
|
|
170
1616
|
@media (max-width: 760px) {
|
|
171
|
-
.toolbar { align-content: center; gap: 6px; padding: 6px; }
|
|
172
|
-
.toolbar-title { flex: 1 0 calc(100% - 12px); }
|
|
173
|
-
.toolbar-separator { display: none; }
|
|
174
|
-
.toolbar-group { flex: 1 1 100%; gap: 5px; }
|
|
175
|
-
.toolbar-group button { padding-inline: 7px; }
|
|
176
|
-
.toolbar input[type="range"] { width: min(24vw, 86px); }
|
|
177
|
-
.toolbar input[type="text"] { width: min(34vw, 130px); }
|
|
178
1617
|
.workspace { grid-template-columns: minmax(0, 1fr); }
|
|
179
1618
|
.inspector { position: absolute; z-index: 20; right: 0; inset-block: 0; width: min(92vw, 390px); box-shadow: -8px 0 28px rgba(0,0,0,.18); }
|
|
180
1619
|
}
|
|
181
1620
|
@media (prefers-reduced-motion: reduce) {
|
|
182
1621
|
*, *::before, *::after { scroll-behavior: auto !important; transition-duration: .01ms !important; animation-duration: .01ms !important; animation-iteration-count: 1 !important; }
|
|
183
1622
|
}
|
|
1623
|
+
${workbenchStyles}
|
|
1624
|
+
${controlsStyles}
|
|
1625
|
+
${devtoolsStyles}
|
|
184
1626
|
</style>
|
|
185
1627
|
</head>
|
|
186
1628
|
<body>
|
|
187
|
-
<header class="toolbar" aria-label="Comparison controls">
|
|
188
|
-
<span class="toolbar-title" id="title"></span>
|
|
189
|
-
<div class="toolbar-group" role="group" aria-label="Comparison mode">
|
|
190
|
-
<button type="button" data-mode="side-by-side">Side by side</button>
|
|
191
|
-
<button type="button" data-mode="split">Split</button>
|
|
192
|
-
<button type="button" data-mode="overlay">Overlay</button>
|
|
193
|
-
<button type="button" data-mode="blink">Blink</button>
|
|
194
|
-
</div>
|
|
195
|
-
<span class="toolbar-separator"></span>
|
|
196
|
-
<div class="toolbar-group">
|
|
197
|
-
<label id="split-control">Split <input id="split" type="range" min="5" max="95" value="50" aria-label="Split position" /></label>
|
|
198
|
-
<label id="alpha-control">Opacity <input id="alpha" type="range" min="0" max="100" value="50" aria-label="Overlay opacity" /></label>
|
|
199
|
-
<button type="button" id="driver">Interact: left</button>
|
|
200
|
-
<button type="button" id="blink-pause">Pause blink</button>
|
|
201
|
-
<button type="button" id="inspect-toggle" aria-controls="inspector" aria-expanded="false" aria-pressed="false">Inspect</button>
|
|
202
|
-
</div>
|
|
203
|
-
<span class="toolbar-separator"></span>
|
|
204
|
-
<div class="toolbar-group">
|
|
205
|
-
<input id="scene-name" type="text" value="comparison-view" aria-label="Scene name" />
|
|
206
|
-
<label>DPR <select id="scene-dpr"><option value="1">1</option><option value="2">2</option></select></label>
|
|
207
|
-
<button type="button" id="copy-scene">Copy scene</button>
|
|
208
|
-
<button type="button" id="copy-command">Copy command</button>
|
|
209
|
-
</div>
|
|
210
|
-
<span class="feedback" id="feedback" role="status"></span>
|
|
211
|
-
</header>
|
|
212
1629
|
<div class="workspace">
|
|
213
1630
|
<main class="stage" id="stage" data-driver="left" data-tileflow-capture-id="tileflow-comparison" data-tileflow-state="loading">
|
|
214
1631
|
<section class="pane pane-left" data-side="left">
|
|
215
|
-
<
|
|
216
|
-
<iframe id="left-frame" title="Left Tileflow preview" referrerpolicy="no-referrer"></iframe>
|
|
1632
|
+
<iframe id="left-frame" title="${options.right ? "Left Tileflow preview" : "Tileflow map preview"}" referrerpolicy="no-referrer"></iframe>
|
|
217
1633
|
</section>
|
|
218
1634
|
<section class="pane pane-right" data-side="right">
|
|
219
|
-
<div class="pane-label"><span class="side-state" id="right-state" role="status" aria-live="polite"></span><span class="pane-label-text" id="right-label"></span><span class="side-state-text" id="right-status-text">Building</span></div>
|
|
220
1635
|
<iframe id="right-frame" title="Right Tileflow preview" referrerpolicy="no-referrer"></iframe>
|
|
221
1636
|
</section>
|
|
222
1637
|
<div class="split-line" aria-hidden="true"></div>
|
|
1638
|
+
<div class="inspect-hint" id="wb-inspect-hint" hidden>Click an element to inspect \xB7 Esc to exit</div>
|
|
223
1639
|
</main>
|
|
224
1640
|
<aside class="inspector" id="inspector" tabindex="-1" aria-labelledby="inspector-title" hidden>
|
|
225
|
-
<
|
|
226
|
-
<
|
|
227
|
-
<
|
|
1641
|
+
<button type="button" id="inspect-toggle" aria-controls="inspector" aria-expanded="false" aria-pressed="false" aria-keyshortcuts="i" title="Select a map element (I). Escape to stop.">${workbenchIcon("inspect")}Inspect</button>
|
|
1642
|
+
<button type="button" id="controls-toggle" aria-controls="map-controls" aria-expanded="false">Scene</button>
|
|
1643
|
+
<div class="inspector-heading"><h2 id="inspector-title">${workbenchIcon("inspect")} Inspect the map</h2><button type="button" id="inspector-close" aria-label="Close inspector">${workbenchIcon("close")}</button></div>
|
|
1644
|
+
<div id="inspection" class="empty">Click a feature to understand how it is drawn and find its configuration.</div>
|
|
1645
|
+
<details class="zoom-details"><summary>Appearance at different zoom levels</summary>
|
|
228
1646
|
<select id="curve-property" disabled><option>Select a rendered layer first</option></select>
|
|
229
|
-
<div id="curve-output" class="empty">
|
|
230
|
-
<h3>Compiler sidecar</h3>
|
|
231
|
-
<
|
|
232
|
-
<h3>Sprite atlas</h3>
|
|
1647
|
+
<div id="curve-output" class="empty">Select an element to explore its appearance.</div></details>
|
|
1648
|
+
<details><summary>Compiler details</summary><h3>Compiler sidecar</h3><pre id="sidecar-output">No layer selected.</pre></details>
|
|
1649
|
+
<details><summary>Sprite atlas</summary>
|
|
233
1650
|
<button type="button" id="load-sprites">Load active side sprites</button>
|
|
234
|
-
<div id="sprite-output" class="sprite-grid"></div>
|
|
1651
|
+
<div id="sprite-output" class="sprite-grid"></div></details>
|
|
235
1652
|
</aside>
|
|
1653
|
+
${workbenchPanels}
|
|
1654
|
+
${controlsPanel}
|
|
236
1655
|
</div>
|
|
237
1656
|
<script type="module"${nonceAttribute}>
|
|
238
1657
|
const options = ${documentOptions};
|
|
@@ -250,24 +1669,16 @@ function renderTileflowComparisonHtml(options) {
|
|
|
250
1669
|
const curveOutput = document.getElementById("curve-output");
|
|
251
1670
|
const sidecarOutput = document.getElementById("sidecar-output");
|
|
252
1671
|
const spriteOutput = document.getElementById("sprite-output");
|
|
253
|
-
const feedback = document.getElementById("feedback");
|
|
254
|
-
const splitInput = document.getElementById("split");
|
|
255
|
-
const alphaInput = document.getElementById("alpha");
|
|
256
|
-
const driverButton = document.getElementById("driver");
|
|
257
|
-
const blinkPauseButton = document.getElementById("blink-pause");
|
|
258
1672
|
const inspectButton = document.getElementById("inspect-toggle");
|
|
259
1673
|
const inspectorCloseButton = document.getElementById("inspector-close");
|
|
260
|
-
const sceneNameInput = document.getElementById("scene-name");
|
|
261
|
-
const sceneDprInput = document.getElementById("scene-dpr");
|
|
262
1674
|
const reducedMotion = matchMedia("(prefers-reduced-motion: reduce)");
|
|
263
|
-
const
|
|
1675
|
+
const sideIds = options.right ? ["left", "right"] : ["left"];
|
|
1676
|
+
const states = Object.fromEntries(sideIds.map((id) => {
|
|
264
1677
|
const definition = options[id];
|
|
265
1678
|
return [id, {
|
|
266
1679
|
id,
|
|
267
1680
|
definition,
|
|
268
1681
|
frame: document.getElementById(id + "-frame"),
|
|
269
|
-
stateElement: document.getElementById(id + "-state"),
|
|
270
|
-
statusTextElement: document.getElementById(id + "-status-text"),
|
|
271
1682
|
map: undefined,
|
|
272
1683
|
camera: undefined,
|
|
273
1684
|
bridgeReady: false,
|
|
@@ -296,45 +1707,25 @@ function renderTileflowComparisonHtml(options) {
|
|
|
296
1707
|
let spriteLoadSequence = 0;
|
|
297
1708
|
let spriteOwnerId;
|
|
298
1709
|
|
|
299
|
-
document.getElementById("title").textContent = options.title;
|
|
300
1710
|
const initialUi = readUiFromUrl();
|
|
301
|
-
|
|
302
|
-
alphaInput.value = String(initialUi.alpha);
|
|
1711
|
+
|
|
303
1712
|
applySliders();
|
|
304
1713
|
setMode(initialUi.mode || options.initialMode);
|
|
305
|
-
|
|
306
|
-
for (const id of ["left", "right"]) {
|
|
1714
|
+
for (const id of sideIds) {
|
|
307
1715
|
const side = states[id];
|
|
308
|
-
document.getElementById(id + "-label").textContent = side.definition.label;
|
|
309
1716
|
side.frame.src = side.definition.previewUrl;
|
|
310
1717
|
side.frame.addEventListener("load", () => connectSide(side));
|
|
311
1718
|
side.frame.addEventListener("focus", () => selectSide(side.id));
|
|
312
1719
|
startStatus(side);
|
|
313
1720
|
}
|
|
314
1721
|
|
|
315
|
-
for (const button of document.querySelectorAll("[data-mode]")) {
|
|
316
|
-
button.addEventListener("click", () => setMode(button.dataset.mode));
|
|
317
|
-
}
|
|
318
|
-
splitInput.addEventListener("input", () => { applySliders(); writeUrlState(); });
|
|
319
|
-
alphaInput.addEventListener("input", () => { applySliders(); writeUrlState(); });
|
|
320
|
-
driverButton.addEventListener("click", () => {
|
|
321
|
-
selectSide(stage.dataset.driver === "left" ? "right" : "left");
|
|
322
|
-
});
|
|
323
|
-
blinkPauseButton.addEventListener("click", () => {
|
|
324
|
-
blinkPaused = !blinkPaused;
|
|
325
|
-
updateBlinkMotionPreference();
|
|
326
|
-
restartBlink();
|
|
327
|
-
});
|
|
328
1722
|
inspectButton.addEventListener("click", () => setInspectionEnabled(!inspectionEnabled, true));
|
|
329
1723
|
inspectorCloseButton.addEventListener("click", () => setInspectionEnabled(false, true));
|
|
330
|
-
document.getElementById("copy-scene").addEventListener("click", () => copyScene(false));
|
|
331
|
-
document.getElementById("copy-command").addEventListener("click", () => copyScene(true));
|
|
332
1724
|
document.getElementById("load-sprites").addEventListener("click", () => loadSprites(states[activeSideId]));
|
|
333
1725
|
curveProperty.addEventListener("change", renderCurve);
|
|
334
1726
|
reducedMotion.addEventListener?.("change", () => {
|
|
335
1727
|
blinkPaused = reducedMotion.matches;
|
|
336
|
-
|
|
337
|
-
restartBlink();
|
|
1728
|
+
restartBlink();
|
|
338
1729
|
});
|
|
339
1730
|
addEventListener("keydown", (event) => {
|
|
340
1731
|
if (event.key === "Escape" && inspectionEnabled) setInspectionEnabled(false, true);
|
|
@@ -346,18 +1737,16 @@ function renderTileflowComparisonHtml(options) {
|
|
|
346
1737
|
side.eventSource?.close();
|
|
347
1738
|
side.sidecarAbort?.abort();
|
|
348
1739
|
side.spriteAbort?.abort();
|
|
349
|
-
side
|
|
1740
|
+
cleanupSideBridge(side);
|
|
350
1741
|
}
|
|
351
1742
|
});
|
|
352
1743
|
|
|
353
1744
|
function selectSide(id, writeState = true) {
|
|
354
|
-
if (id
|
|
1745
|
+
if (!states[id]) return;
|
|
355
1746
|
const changed = activeSideId !== id || stage.dataset.driver !== id;
|
|
356
1747
|
const previousSpriteOwner = spriteOwnerId;
|
|
357
1748
|
activeSideId = id;
|
|
358
1749
|
stage.dataset.driver = id;
|
|
359
|
-
driverButton.textContent = "Interact: " + id;
|
|
360
|
-
driverButton.setAttribute("aria-label", "Interact with " + states[id].definition.label);
|
|
361
1750
|
if (changed && previousSpriteOwner && previousSpriteOwner !== id) {
|
|
362
1751
|
states[previousSpriteOwner].spriteAbort?.abort();
|
|
363
1752
|
spriteLoadSequence += 1;
|
|
@@ -370,33 +1759,31 @@ function renderTileflowComparisonHtml(options) {
|
|
|
370
1759
|
|
|
371
1760
|
function setInspectionEnabled(enabled, manageFocus = false) {
|
|
372
1761
|
inspectionEnabled = enabled;
|
|
1762
|
+
if (globalThis.__tileflowDevtoolsUI) {
|
|
1763
|
+
inspectButton.setAttribute("aria-pressed", String(enabled));
|
|
1764
|
+
document.getElementById("wb-inspect-hint").hidden = !enabled;
|
|
1765
|
+
for (const side of Object.values(states)) sendInspectionState(side);
|
|
1766
|
+
globalThis.__tileflowDevtoolsUI.inspectionChanged(enabled);
|
|
1767
|
+
return;
|
|
1768
|
+
}
|
|
1769
|
+
if (enabled) toggleMapControls(false);
|
|
373
1770
|
inspectButton.setAttribute("aria-pressed", String(enabled));
|
|
374
1771
|
inspectButton.setAttribute("aria-expanded", String(enabled));
|
|
375
1772
|
inspector.hidden = !enabled;
|
|
376
|
-
|
|
1773
|
+
document.getElementById("wb-inspect-hint").hidden = !enabled;
|
|
1774
|
+
for (const side of Object.values(states)) { sendInspectionState(side); side.map?.resize?.(); }
|
|
377
1775
|
if (manageFocus) {
|
|
378
1776
|
if (enabled) inspector.focus();
|
|
379
1777
|
else inspectButton.focus();
|
|
380
1778
|
}
|
|
381
1779
|
}
|
|
382
1780
|
|
|
383
|
-
function updateBlinkMotionPreference() {
|
|
384
|
-
const motionBlocked = reducedMotion.matches;
|
|
385
|
-
blinkPauseButton.disabled = motionBlocked;
|
|
386
|
-
blinkPauseButton.textContent = motionBlocked
|
|
387
|
-
? "Blink paused (reduced motion)"
|
|
388
|
-
: blinkPaused ? "Resume blink" : "Pause blink";
|
|
389
|
-
}
|
|
390
1781
|
|
|
391
1782
|
function setMode(mode) {
|
|
392
|
-
if (!
|
|
1783
|
+
if (!options.right) mode = "single";
|
|
1784
|
+
if (!["single", "side-by-side", "split", "overlay", "blink"].includes(mode)) return;
|
|
1785
|
+
if (mode === "single" && activeSideId !== "left") selectSide("left", false);
|
|
393
1786
|
stage.dataset.mode = mode;
|
|
394
|
-
for (const button of document.querySelectorAll("[data-mode]")) {
|
|
395
|
-
button.setAttribute("aria-pressed", String(button.dataset.mode === mode));
|
|
396
|
-
}
|
|
397
|
-
document.getElementById("split-control").hidden = mode !== "split";
|
|
398
|
-
document.getElementById("alpha-control").hidden = mode !== "overlay";
|
|
399
|
-
blinkPauseButton.hidden = mode !== "blink";
|
|
400
1787
|
restartBlink();
|
|
401
1788
|
writeUrlState();
|
|
402
1789
|
setTimeout(() => {
|
|
@@ -405,8 +1792,8 @@ function renderTileflowComparisonHtml(options) {
|
|
|
405
1792
|
}
|
|
406
1793
|
|
|
407
1794
|
function applySliders() {
|
|
408
|
-
const split = boundedNumber(
|
|
409
|
-
const alpha = boundedNumber(
|
|
1795
|
+
const split = boundedNumber(initialUi.split, 5, 95, 50);
|
|
1796
|
+
const alpha = boundedNumber(initialUi.alpha, 0, 100, 50);
|
|
410
1797
|
stage.style.setProperty("--split", split + "%");
|
|
411
1798
|
stage.style.setProperty("--overlay-alpha", String(alpha / 100));
|
|
412
1799
|
}
|
|
@@ -423,16 +1810,27 @@ function renderTileflowComparisonHtml(options) {
|
|
|
423
1810
|
}, 700);
|
|
424
1811
|
}
|
|
425
1812
|
|
|
1813
|
+
function cleanupSideBridge(side) {
|
|
1814
|
+
try { side.cleanupBridge?.(); }
|
|
1815
|
+
catch (error) {
|
|
1816
|
+
// A stopped dev server can replace the iframe with a cross-origin browser error page.
|
|
1817
|
+
if (error?.name !== "SecurityError") throw error;
|
|
1818
|
+
} finally { side.cleanupBridge = undefined; }
|
|
1819
|
+
}
|
|
1820
|
+
|
|
426
1821
|
function connectSide(side) {
|
|
427
1822
|
side.loadEpoch += 1;
|
|
1823
|
+
side.appliedSceneStyle = undefined;
|
|
1824
|
+
side.pendingAppearance = undefined;
|
|
428
1825
|
const epoch = side.loadEpoch;
|
|
429
|
-
side
|
|
1826
|
+
cleanupSideBridge(side);
|
|
430
1827
|
side.cleanupBridge = undefined;
|
|
431
1828
|
side.sidecarAbort?.abort();
|
|
432
1829
|
side.spriteAbort?.abort();
|
|
433
1830
|
side.sidecarAbort = undefined;
|
|
434
1831
|
side.spriteAbort = undefined;
|
|
435
1832
|
side.map = undefined;
|
|
1833
|
+
side.bridgeConnected = false;
|
|
436
1834
|
side.bridgeReady = false;
|
|
437
1835
|
side.camera = undefined;
|
|
438
1836
|
side.sidecar = undefined;
|
|
@@ -456,7 +1854,6 @@ function renderTileflowComparisonHtml(options) {
|
|
|
456
1854
|
curveOutput.textContent = "Paint and layout values appear here.";
|
|
457
1855
|
sidecarOutput.textContent = "No layer selected.";
|
|
458
1856
|
}
|
|
459
|
-
renderSideStatus(side);
|
|
460
1857
|
updateDocumentReadiness();
|
|
461
1858
|
const started = performance.now();
|
|
462
1859
|
const findMap = () => {
|
|
@@ -467,7 +1864,13 @@ function renderTileflowComparisonHtml(options) {
|
|
|
467
1864
|
const map = childWindow?.__tileflowPreviewMap;
|
|
468
1865
|
if (map) {
|
|
469
1866
|
side.map = map;
|
|
1867
|
+
// DevTools can edit a connected map while its tiles are still loading.
|
|
1868
|
+
// bridgeReady remains the separate renderer-readiness signal.
|
|
1869
|
+
side.bridgeConnected = true;
|
|
1870
|
+
side.camera = readMapCamera(map);
|
|
470
1871
|
side.cleanupBridge = installChildBridge(side, childWindow, map, epoch);
|
|
1872
|
+
if (options.workbenchUrl) void refreshWorkbench();
|
|
1873
|
+
if (!controlsPanel.hidden) void loadMapControls();
|
|
471
1874
|
return;
|
|
472
1875
|
}
|
|
473
1876
|
} catch (error) {
|
|
@@ -493,6 +1896,68 @@ function renderTileflowComparisonHtml(options) {
|
|
|
493
1896
|
let releaseFrameAfterPaint;
|
|
494
1897
|
let readyTimer;
|
|
495
1898
|
let readyAnnounced = false;
|
|
1899
|
+
let inspectedFeatures = [];
|
|
1900
|
+
let highlightedIndex = -1;
|
|
1901
|
+
let hoveredFeature, hoveredCoordinate, hoverFrame, hoverHint;
|
|
1902
|
+
let inspectedCoordinate;
|
|
1903
|
+
let highlight;
|
|
1904
|
+
const drawHighlight = () => {
|
|
1905
|
+
const geometry = hoveredFeature?.geometry || inspectedFeatures[highlightedIndex]?.geometry;
|
|
1906
|
+
if ((!inspectionActive && !globalThis.__tileflowDevtoolsUI) || !geometry || !map.getContainer || !map.project) { if (highlight) highlight.style.display = "none"; return; }
|
|
1907
|
+
if (!highlight) {
|
|
1908
|
+
highlight = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
1909
|
+
highlight.setAttribute("aria-hidden", "true");
|
|
1910
|
+
highlight.style.cssText = "position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:3;overflow:hidden";
|
|
1911
|
+
map.getContainer().append(highlight);
|
|
1912
|
+
}
|
|
1913
|
+
highlight.style.display = "";
|
|
1914
|
+
highlight.replaceChildren();
|
|
1915
|
+
let remaining = 3000;
|
|
1916
|
+
const project = (coordinate) => {
|
|
1917
|
+
if (--remaining < 0 || !Array.isArray(coordinate) || !coordinate.slice(0,2).every(Number.isFinite)) return;
|
|
1918
|
+
const point = map.project(coordinate);
|
|
1919
|
+
return Number.isFinite(point.x) && Number.isFinite(point.y) ? point : undefined;
|
|
1920
|
+
};
|
|
1921
|
+
const draw = (coordinates, closed = false) => {
|
|
1922
|
+
if (!Array.isArray(coordinates)) return;
|
|
1923
|
+
const points = coordinates.slice(0, Math.max(0,remaining)).map(project).filter(Boolean);
|
|
1924
|
+
if (!points.length) return;
|
|
1925
|
+
const shape = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
1926
|
+
shape.setAttribute("d", points.map((point,index) => (index ? "L" : "M") + point.x + " " + point.y).join(" ") + (closed ? "Z" : ""));
|
|
1927
|
+
shape.setAttribute("fill", closed ? "#1a73e822" : "none");
|
|
1928
|
+
shape.setAttribute("stroke", "#1a73e8"); shape.setAttribute("stroke-width", "3");
|
|
1929
|
+
shape.setAttribute("stroke-linejoin", "round"); highlight.append(shape);
|
|
1930
|
+
};
|
|
1931
|
+
const coordinates = geometry.coordinates;
|
|
1932
|
+
const containsPoint = (ring) => {
|
|
1933
|
+
const coordinate = hoveredCoordinate || inspectedCoordinate;
|
|
1934
|
+
if (!coordinate) return false;
|
|
1935
|
+
const [x,y] = coordinate;
|
|
1936
|
+
let inside = false;
|
|
1937
|
+
for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
|
|
1938
|
+
const [xi,yi] = ring[i], [xj,yj] = ring[j];
|
|
1939
|
+
if ((yi > y) !== (yj > y) && x < (xj-xi)*(y-yi)/(yj-yi)+xi) inside = !inside;
|
|
1940
|
+
}
|
|
1941
|
+
return inside;
|
|
1942
|
+
};
|
|
1943
|
+
if (geometry.type === "Polygon") coordinates.forEach((ring) => draw(ring,true));
|
|
1944
|
+
else if (geometry.type === "MultiPolygon") coordinates.filter((polygon) => containsPoint(polygon[0]) && !polygon.slice(1).some(containsPoint)).forEach((polygon) => polygon.forEach((ring) => draw(ring,true)));
|
|
1945
|
+
else if (geometry.type === "LineString") draw(coordinates);
|
|
1946
|
+
else if (geometry.type === "MultiLineString") coordinates.forEach((line) => draw(line));
|
|
1947
|
+
else for (const coordinate of geometry.type === "MultiPoint" ? coordinates : [coordinates]) {
|
|
1948
|
+
const point = project(coordinate); if (!point) continue;
|
|
1949
|
+
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
1950
|
+
circle.setAttribute("cx", point.x); circle.setAttribute("cy", point.y); circle.setAttribute("r", "10");
|
|
1951
|
+
circle.setAttribute("fill", "#1a73e822"); circle.setAttribute("stroke", "#1a73e8"); circle.setAttribute("stroke-width", "3"); highlight.append(circle);
|
|
1952
|
+
}
|
|
1953
|
+
};
|
|
1954
|
+
side.highlightFeature = (index) => { highlightedIndex = index; drawHighlight(); };
|
|
1955
|
+
side.getInspectedFeature = () => inspectedFeatures[highlightedIndex];
|
|
1956
|
+
const handleKey = (event) => {
|
|
1957
|
+
if (event.target.closest?.("input,select,textarea,[contenteditable]")) return;
|
|
1958
|
+
if (event.key === "Escape" && inspectionEnabled) { event.preventDefault(); setInspectionEnabled(false, true); }
|
|
1959
|
+
if (event.key.toLowerCase() === "i" && !event.metaKey && !event.ctrlKey && !event.altKey) { event.preventDefault(); setInspectionEnabled(!inspectionEnabled, true); }
|
|
1960
|
+
};
|
|
496
1961
|
const targetOrigin = childWindow.location.origin;
|
|
497
1962
|
// The bridge is installed by the same-origin parent, so callbacks created here
|
|
498
1963
|
// belong to the parent realm. Dispatch directly with an explicit child source;
|
|
@@ -515,11 +1980,11 @@ function renderTileflowComparisonHtml(options) {
|
|
|
515
1980
|
});
|
|
516
1981
|
};
|
|
517
1982
|
const handleMoveStart = (event) => {
|
|
518
|
-
if (applyingRequestId === undefined && event?.originalEvent) userMovementActive = true;
|
|
1983
|
+
if (applyingRequestId === undefined && (event?.originalEvent || event?.tileflowDevtools === true)) userMovementActive = true;
|
|
519
1984
|
};
|
|
520
1985
|
const handleMove = (event) => {
|
|
521
1986
|
if (applyingRequestId !== undefined) return;
|
|
522
|
-
if (event?.originalEvent) userMovementActive = true;
|
|
1987
|
+
if (event?.originalEvent || event?.tileflowDevtools === true) userMovementActive = true;
|
|
523
1988
|
queuedUserMovement = queuedUserMovement || userMovementActive;
|
|
524
1989
|
if (movementFrame !== undefined) return;
|
|
525
1990
|
movementFrame = childWindow.requestAnimationFrame(() => {
|
|
@@ -535,7 +2000,7 @@ function renderTileflowComparisonHtml(options) {
|
|
|
535
2000
|
scheduleApplyingRelease(applyingRequestId);
|
|
536
2001
|
return;
|
|
537
2002
|
}
|
|
538
|
-
if (event?.originalEvent) userMovementActive = true;
|
|
2003
|
+
if (event?.originalEvent || event?.tileflowDevtools === true) userMovementActive = true;
|
|
539
2004
|
if (movementFrame !== undefined) {
|
|
540
2005
|
childWindow.cancelAnimationFrame(movementFrame);
|
|
541
2006
|
movementFrame = undefined;
|
|
@@ -544,9 +2009,45 @@ function renderTileflowComparisonHtml(options) {
|
|
|
544
2009
|
postCamera("end", undefined, userMovementActive);
|
|
545
2010
|
userMovementActive = false;
|
|
546
2011
|
};
|
|
2012
|
+
const clearHover = () => {
|
|
2013
|
+
if (hoverFrame !== undefined) childWindow.cancelAnimationFrame(hoverFrame);
|
|
2014
|
+
hoverFrame = undefined; hoveredFeature = undefined; hoveredCoordinate = undefined;
|
|
2015
|
+
if (hoverHint) hoverHint.hidden = true;
|
|
2016
|
+
drawHighlight();
|
|
2017
|
+
};
|
|
2018
|
+
side.hoverFeature = (index) => {
|
|
2019
|
+
clearHover();
|
|
2020
|
+
if (!inspectedFeatures[index]) return;
|
|
2021
|
+
hoveredFeature = inspectedFeatures[index];
|
|
2022
|
+
hoveredCoordinate = inspectedCoordinate;
|
|
2023
|
+
drawHighlight();
|
|
2024
|
+
};
|
|
2025
|
+
const handleHover = (event) => {
|
|
2026
|
+
if (!inspectionActive || !globalThis.__tileflowDevtoolsUI || hoverFrame !== undefined) return;
|
|
2027
|
+
hoverFrame = childWindow.requestAnimationFrame(() => {
|
|
2028
|
+
hoverFrame = undefined;
|
|
2029
|
+
hoveredFeature = map.queryRenderedFeatures(event.point).find((feature) => !feature.layer.id.startsWith("__tileflow-devtools-"));
|
|
2030
|
+
hoveredCoordinate = [event.lngLat.lng, event.lngLat.lat];
|
|
2031
|
+
if (!hoverHint) {
|
|
2032
|
+
hoverHint = document.createElement("div");
|
|
2033
|
+
hoverHint.style.cssText = "position:absolute;z-index:6;pointer-events:none;padding:4px 7px;background:#fff;color:#202124;border:1px solid #dadce0;border-radius:3px;box-shadow:0 2px 5px #0003;font:11px/1.5 monospace;max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap";
|
|
2034
|
+
map.getContainer().append(hoverHint);
|
|
2035
|
+
}
|
|
2036
|
+
hoverHint.hidden = !hoveredFeature;
|
|
2037
|
+
if (hoveredFeature) {
|
|
2038
|
+
hoverHint.textContent = hoveredFeature.layer.id + " \xB7 " + hoveredFeature.geometry.type + (hoveredFeature.id === undefined ? "" : " #" + hoveredFeature.id);
|
|
2039
|
+
hoverHint.style.left = Math.max(4,Math.min(event.point.x + 14,map.getContainer().clientWidth - 310)) + "px";
|
|
2040
|
+
hoverHint.style.top = Math.max(4,Math.min(event.point.y + 14,map.getContainer().clientHeight - 32)) + "px";
|
|
2041
|
+
}
|
|
2042
|
+
drawHighlight();
|
|
2043
|
+
});
|
|
2044
|
+
};
|
|
547
2045
|
const handleClick = (event) => {
|
|
548
2046
|
if (!inspectionActive) return;
|
|
549
|
-
|
|
2047
|
+
clearHover();
|
|
2048
|
+
inspectedCoordinate = [event.lngLat.lng, event.lngLat.lat];
|
|
2049
|
+
inspectedFeatures = map.queryRenderedFeatures(event.point).filter((feature) => !feature.layer.id.startsWith("__tileflow-devtools-")).slice(0, 16);
|
|
2050
|
+
const features = inspectedFeatures.map(projectFeature);
|
|
550
2051
|
postToParent({
|
|
551
2052
|
type: bridgeInspectionType,
|
|
552
2053
|
schemaVersion: bridgeSchemaVersion,
|
|
@@ -555,6 +2056,13 @@ function renderTileflowComparisonHtml(options) {
|
|
|
555
2056
|
features
|
|
556
2057
|
});
|
|
557
2058
|
};
|
|
2059
|
+
side.inspectRenderedFeature = (feature, coordinate) => {
|
|
2060
|
+
clearHover();
|
|
2061
|
+
inspectedCoordinate = coordinate;
|
|
2062
|
+
inspectedFeatures = [feature];
|
|
2063
|
+
postToParent({type: bridgeInspectionType, schemaVersion: bridgeSchemaVersion,
|
|
2064
|
+
side: side.id, coordinate, features: [projectFeature(feature)]});
|
|
2065
|
+
};
|
|
558
2066
|
const handleError = (event) => {
|
|
559
2067
|
postToParent({
|
|
560
2068
|
type: bridgeRuntimeErrorType,
|
|
@@ -569,7 +2077,9 @@ function renderTileflowComparisonHtml(options) {
|
|
|
569
2077
|
if (!message || message.schemaVersion !== bridgeSchemaVersion) return;
|
|
570
2078
|
if (message.type === bridgeSetInspectionType && typeof message.enabled === "boolean") {
|
|
571
2079
|
inspectionActive = message.enabled;
|
|
2080
|
+
if (!inspectionActive) clearHover();
|
|
572
2081
|
map.getCanvas().style.cursor = inspectionActive ? "crosshair" : "";
|
|
2082
|
+
drawHighlight();
|
|
573
2083
|
return;
|
|
574
2084
|
}
|
|
575
2085
|
if (message.type !== bridgeSetCameraType || !validCamera(message.camera)) return;
|
|
@@ -622,10 +2132,15 @@ function renderTileflowComparisonHtml(options) {
|
|
|
622
2132
|
});
|
|
623
2133
|
});
|
|
624
2134
|
};
|
|
2135
|
+
map.on("move", drawHighlight);
|
|
2136
|
+
map.on("resize", drawHighlight);
|
|
2137
|
+
childWindow.addEventListener("keydown", handleKey);
|
|
625
2138
|
map.on("movestart", handleMoveStart);
|
|
626
2139
|
map.on("move", handleMove);
|
|
627
2140
|
map.on("moveend", handleMoveEnd);
|
|
628
2141
|
map.on("click", handleClick);
|
|
2142
|
+
map.on("mousemove", handleHover);
|
|
2143
|
+
map.on("mouseout", clearHover);
|
|
629
2144
|
map.on("error", handleError);
|
|
630
2145
|
childWindow.addEventListener("message", handleCommand);
|
|
631
2146
|
if (map.loaded?.()) announceReady();
|
|
@@ -638,10 +2153,17 @@ function renderTileflowComparisonHtml(options) {
|
|
|
638
2153
|
if (movementFrame !== undefined) childWindow.cancelAnimationFrame(movementFrame);
|
|
639
2154
|
cancelApplyingRelease();
|
|
640
2155
|
clearTimeout(readyTimer);
|
|
2156
|
+
clearHover(); hoverHint?.remove();
|
|
2157
|
+
highlight?.remove(); side.highlightFeature = undefined; side.hoverFeature = undefined;
|
|
2158
|
+
side.getInspectedFeature = undefined; side.inspectRenderedFeature = undefined;
|
|
2159
|
+
map.off("move", drawHighlight); map.off("resize", drawHighlight);
|
|
2160
|
+
childWindow.removeEventListener("keydown", handleKey);
|
|
641
2161
|
map.off("movestart", handleMoveStart);
|
|
642
2162
|
map.off("move", handleMove);
|
|
643
2163
|
map.off("moveend", handleMoveEnd);
|
|
644
2164
|
map.off("click", handleClick);
|
|
2165
|
+
map.off("mousemove", handleHover);
|
|
2166
|
+
map.off("mouseout", clearHover);
|
|
645
2167
|
map.off("error", handleError);
|
|
646
2168
|
map.off("load", announceReady);
|
|
647
2169
|
map.off("idle", announceReady);
|
|
@@ -657,10 +2179,11 @@ function renderTileflowComparisonHtml(options) {
|
|
|
657
2179
|
if (message.type === bridgeReadyType && validCamera(message.camera)) {
|
|
658
2180
|
side.bridgeReady = true;
|
|
659
2181
|
side.camera = message.camera;
|
|
660
|
-
renderSideStatus(side);
|
|
661
2182
|
maybeEstablishInitialCamera();
|
|
662
2183
|
sendInspectionState(side);
|
|
663
2184
|
void loadSidecar(side);
|
|
2185
|
+
if (options.workbenchUrl) void refreshWorkbench();
|
|
2186
|
+
if (!controlsPanel.hidden) void loadMapControls();
|
|
664
2187
|
updateDocumentReadiness();
|
|
665
2188
|
return;
|
|
666
2189
|
}
|
|
@@ -700,7 +2223,7 @@ function renderTileflowComparisonHtml(options) {
|
|
|
700
2223
|
if (message.type === bridgeInspectionType && Array.isArray(message.features)) {
|
|
701
2224
|
selectSide(side.id);
|
|
702
2225
|
selectedInspection = {side, coordinate: message.coordinate, features: message.features.slice(0, 16), featureIndex: 0};
|
|
703
|
-
renderInspection();
|
|
2226
|
+
renderInspection(true);
|
|
704
2227
|
return;
|
|
705
2228
|
}
|
|
706
2229
|
if (message.type === bridgeRuntimeErrorType) {
|
|
@@ -709,7 +2232,7 @@ function renderTileflowComparisonHtml(options) {
|
|
|
709
2232
|
}
|
|
710
2233
|
|
|
711
2234
|
function sendCamera(side, camera) {
|
|
712
|
-
if (!side
|
|
2235
|
+
if (!side?.bridgeReady || !camera || !side.frame.contentWindow) return;
|
|
713
2236
|
if (side.camera && camerasEqual(side.camera, camera)) return;
|
|
714
2237
|
const requestId = ++requestSequence;
|
|
715
2238
|
side.lastCameraRequestId = requestId;
|
|
@@ -759,6 +2282,16 @@ function renderTileflowComparisonHtml(options) {
|
|
|
759
2282
|
.catch(() => undefined);
|
|
760
2283
|
const events = new EventSource(side.definition.eventsUrl);
|
|
761
2284
|
side.eventSource = events;
|
|
2285
|
+
let connected = false, disconnected = false;
|
|
2286
|
+
events.addEventListener("open", () => {
|
|
2287
|
+
if (connected && disconnected) {
|
|
2288
|
+
side.artifactGeneration = -1;
|
|
2289
|
+
side.latestArtifactStatus = undefined;
|
|
2290
|
+
side.frame.contentWindow?.location.reload();
|
|
2291
|
+
}
|
|
2292
|
+
connected = true; disconnected = false;
|
|
2293
|
+
});
|
|
2294
|
+
events.addEventListener("error", () => { if (connected) disconnected = true; });
|
|
762
2295
|
for (const name of ["building", "ready", "invalid"]) {
|
|
763
2296
|
events.addEventListener(name, (event) => {
|
|
764
2297
|
try { applyArtifactStatus(side, JSON.parse(event.data)); } catch {}
|
|
@@ -781,7 +2314,8 @@ function renderTileflowComparisonHtml(options) {
|
|
|
781
2314
|
? status.lastGoodGeneration
|
|
782
2315
|
: undefined;
|
|
783
2316
|
side.artifactDiagnostic = status.diagnostics?.[0]?.message;
|
|
784
|
-
|
|
2317
|
+
side.latestArtifactStatus = status;
|
|
2318
|
+
side.previewStatusReceiver?.(status);
|
|
785
2319
|
maybeEstablishInitialCamera();
|
|
786
2320
|
updateDocumentReadiness();
|
|
787
2321
|
}
|
|
@@ -800,31 +2334,26 @@ function renderTileflowComparisonHtml(options) {
|
|
|
800
2334
|
stage.dataset.tileflowState = value;
|
|
801
2335
|
}
|
|
802
2336
|
|
|
803
|
-
function renderSideStatus(side) {
|
|
804
|
-
const diagnostic = side.artifactDiagnostic;
|
|
805
|
-
const message = side.runtimeFailed
|
|
806
|
-
? "Runtime error: " + (side.runtimeErrors.at(-1) || "Preview runtime failed")
|
|
807
|
-
: side.artifactStatus === "invalid"
|
|
808
|
-
? "Invalid generation; showing last valid preview" + (diagnostic ? ": " + diagnostic : "")
|
|
809
|
-
: "Generation " + String(Math.max(0, side.artifactGeneration)) + " \xB7 " + side.artifactStatus;
|
|
810
|
-
side.stateElement.dataset.status = side.runtimeFailed ? "error" : side.artifactStatus;
|
|
811
|
-
side.stateElement.title = message;
|
|
812
|
-
side.stateElement.setAttribute("aria-label", side.definition.label + ": " + message);
|
|
813
|
-
side.statusTextElement.textContent = side.runtimeFailed ? "Runtime error" : side.artifactStatus;
|
|
814
|
-
}
|
|
815
2337
|
|
|
816
2338
|
async function loadSidecar(side) {
|
|
817
2339
|
if (!side.definition.sidecarUrl) return undefined;
|
|
2340
|
+
const key = currentSidecarUrl(side);
|
|
2341
|
+
if (side.sidecarKey !== key) {
|
|
2342
|
+
side.sidecarAbort?.abort();
|
|
2343
|
+
side.sidecar = undefined;
|
|
2344
|
+
side.sidecarPromise = undefined;
|
|
2345
|
+
side.sidecarKey = key;
|
|
2346
|
+
}
|
|
818
2347
|
if (side.sidecar !== undefined) return side.sidecar;
|
|
819
2348
|
if (side.sidecarPromise) return side.sidecarPromise;
|
|
820
2349
|
const epoch = side.loadEpoch;
|
|
821
2350
|
const controller = new AbortController();
|
|
822
2351
|
side.sidecarAbort?.abort();
|
|
823
2352
|
side.sidecarAbort = controller;
|
|
824
|
-
const promise = fetch(
|
|
2353
|
+
const promise = fetch(key, {cache: "no-store", signal: controller.signal})
|
|
825
2354
|
.then((response) => response.ok ? response.json() : Promise.reject(new Error("Compiler sidecar unavailable")))
|
|
826
2355
|
.then((document) => {
|
|
827
|
-
if (side.loadEpoch !== epoch || controller.signal.aborted) return undefined;
|
|
2356
|
+
if (side.loadEpoch !== epoch || side.sidecarKey !== key || controller.signal.aborted) return undefined;
|
|
828
2357
|
side.sidecar = document;
|
|
829
2358
|
if (selectedInspection?.side === side) renderInspection();
|
|
830
2359
|
return document;
|
|
@@ -862,14 +2391,19 @@ function renderTileflowComparisonHtml(options) {
|
|
|
862
2391
|
return configured.href;
|
|
863
2392
|
}
|
|
864
2393
|
configured.pathname = configured.pathname.slice(0, markerIndex) + marker + map + "/" + theme + ".json";
|
|
2394
|
+
const controls = livePreviewUrl(side).searchParams.get("controls");
|
|
2395
|
+
if (controls) configured.searchParams.set("controls", controls);
|
|
2396
|
+
else configured.searchParams.delete("controls");
|
|
865
2397
|
return configured.href;
|
|
866
2398
|
}
|
|
867
2399
|
|
|
868
|
-
|
|
2400
|
+
// Metadata refreshes update the inspection without navigating away from the active tab.
|
|
2401
|
+
function renderInspection(reveal = false) {
|
|
869
2402
|
if (!selectedInspection) return;
|
|
870
2403
|
const side = selectedInspection.side;
|
|
871
2404
|
const feature = selectedInspection.features[selectedInspection.featureIndex];
|
|
872
2405
|
inspectionOutput.replaceChildren();
|
|
2406
|
+
for (const entry of Object.values(states)) entry.highlightFeature?.(entry === side ? selectedInspection.featureIndex : -1);
|
|
873
2407
|
if (!feature) {
|
|
874
2408
|
inspectionOutput.className = "empty";
|
|
875
2409
|
inspectionOutput.textContent = side.definition.label + ": no rendered feature at this point.";
|
|
@@ -877,6 +2411,7 @@ function renderTileflowComparisonHtml(options) {
|
|
|
877
2411
|
curveOutput.className = "empty";
|
|
878
2412
|
curveOutput.textContent = "Paint and layout values appear here.";
|
|
879
2413
|
sidecarOutput.textContent = "No layer selected.";
|
|
2414
|
+
if (reveal) globalThis.__tileflowDevtoolsUI?.selectionChanged();
|
|
880
2415
|
return;
|
|
881
2416
|
}
|
|
882
2417
|
inspectionOutput.className = "";
|
|
@@ -885,20 +2420,20 @@ function renderTileflowComparisonHtml(options) {
|
|
|
885
2420
|
title.textContent = side.definition.label + " \xB7 " + feature.layerId;
|
|
886
2421
|
const pickerLabel = document.createElement("label");
|
|
887
2422
|
pickerLabel.className = "feature-picker";
|
|
888
|
-
pickerLabel.append(document.createTextNode("
|
|
2423
|
+
pickerLabel.append(document.createTextNode("Elements at this point (" + selectedInspection.features.length + ")"));
|
|
889
2424
|
const picker = document.createElement("select");
|
|
890
2425
|
picker.setAttribute("aria-label", "Rendered feature at inspected point");
|
|
891
2426
|
selectedInspection.features.forEach((candidate, index) => {
|
|
892
2427
|
const option = document.createElement("option");
|
|
893
2428
|
option.value = String(index);
|
|
894
|
-
option.textContent = String(index + 1) + ". " + candidate.layerId + " \xB7 " + candidate.geometryType +
|
|
895
|
-
|
|
2429
|
+
option.textContent = options.workbenchUrl ? featureDisplayName(candidate) + " \xB7 " + candidate.geometryType : String(index + 1) + ". " + candidate.layerId + " \xB7 " + candidate.geometryType + (candidate.id === null ? "" : " \xB7 " + String(candidate.id));
|
|
2430
|
+
option.title = candidate.layerId;
|
|
896
2431
|
picker.append(option);
|
|
897
2432
|
});
|
|
898
2433
|
picker.value = String(selectedInspection.featureIndex);
|
|
899
2434
|
picker.addEventListener("change", () => {
|
|
900
2435
|
selectedInspection.featureIndex = boundedInteger(Number(picker.value), 0, selectedInspection.features.length - 1, 0);
|
|
901
|
-
renderInspection();
|
|
2436
|
+
renderInspection(true);
|
|
902
2437
|
});
|
|
903
2438
|
pickerLabel.append(picker);
|
|
904
2439
|
const summary = document.createElement("pre");
|
|
@@ -915,13 +2450,15 @@ function renderTileflowComparisonHtml(options) {
|
|
|
915
2450
|
filter: layer.filter
|
|
916
2451
|
} : undefined
|
|
917
2452
|
});
|
|
918
|
-
|
|
2453
|
+
if (options.workbenchUrl) renderFeatureSummary(side, feature, layer, pickerLabel, summary);
|
|
2454
|
+
else inspectionOutput.append(title, pickerLabel, summary);
|
|
919
2455
|
populateCurveProperties(layer);
|
|
920
2456
|
const sidecarLayer = findSidecarLayer(side.sidecar, feature.layerId);
|
|
921
2457
|
sidecarOutput.textContent = sidecarLayer
|
|
922
2458
|
? boundedJson(sidecarLayerWithSemanticAttribution(sidecarLayer))
|
|
923
2459
|
: "No compiler sidecar entry for this layer.";
|
|
924
2460
|
if (side.sidecar === undefined && !side.sidecarPromise) void loadSidecar(side);
|
|
2461
|
+
if (reveal) globalThis.__tileflowDevtoolsUI?.selectionChanged();
|
|
925
2462
|
}
|
|
926
2463
|
|
|
927
2464
|
function populateCurveProperties(layer) {
|
|
@@ -1199,103 +2736,14 @@ function renderTileflowComparisonHtml(options) {
|
|
|
1199
2736
|
value.x + value.width <= image.naturalWidth && value.y + value.height <= image.naturalHeight;
|
|
1200
2737
|
}
|
|
1201
2738
|
|
|
1202
|
-
async function copyScene(commandOnly) {
|
|
1203
|
-
const name = sceneNameInput.value;
|
|
1204
|
-
if (!validPortableName(name)) {
|
|
1205
|
-
setFeedback("Use a portable kebab-case scene name.");
|
|
1206
|
-
return;
|
|
1207
|
-
}
|
|
1208
|
-
const side = states[activeSideId];
|
|
1209
|
-
const camera = side.camera || canonicalCamera;
|
|
1210
|
-
if (!camera) {
|
|
1211
|
-
setFeedback("Wait for the maps to become ready.");
|
|
1212
|
-
return;
|
|
1213
|
-
}
|
|
1214
|
-
const style = side.map?.getStyle?.();
|
|
1215
|
-
const map = style?.metadata?.["tileflow:map"];
|
|
1216
|
-
const theme = style?.metadata?.["tileflow:theme"];
|
|
1217
|
-
if (!validPortableName(map) || !validConcreteThemeName(theme)) {
|
|
1218
|
-
setFeedback("The active preview does not expose a concrete map and theme.");
|
|
1219
|
-
return;
|
|
1220
|
-
}
|
|
1221
|
-
const dpr = Number(sceneDprInput.value) === 2 ? 2 : 1;
|
|
1222
|
-
const viewport = readMapViewport(side, dpr);
|
|
1223
|
-
const {width, height} = viewport;
|
|
1224
|
-
if (commandOnly) {
|
|
1225
|
-
const command = [
|
|
1226
|
-
"tileflow capture",
|
|
1227
|
-
...(side.definition.captureConfig
|
|
1228
|
-
? ["--config " + quoteCliArgument(side.definition.captureConfig)]
|
|
1229
|
-
: []),
|
|
1230
|
-
"--map " + quoteCliArgument(map),
|
|
1231
|
-
"--theme " + quoteCliArgument(theme),
|
|
1232
|
-
"--center=" + formatNumber(camera.center[0]) + "," + formatNumber(camera.center[1]),
|
|
1233
|
-
"--zoom=" + formatNumber(camera.zoom),
|
|
1234
|
-
"--bearing=" + formatNumber(camera.bearing),
|
|
1235
|
-
"--pitch=" + formatNumber(camera.pitch),
|
|
1236
|
-
"--width=" + width,
|
|
1237
|
-
"--height=" + height,
|
|
1238
|
-
"--dpr=" + dpr,
|
|
1239
|
-
"--out=" + quoteCliArgument(name + ".png")
|
|
1240
|
-
].join(" ");
|
|
1241
|
-
if (!await copyText(command)) {
|
|
1242
|
-
setFeedback("Clipboard access is unavailable.");
|
|
1243
|
-
return;
|
|
1244
|
-
}
|
|
1245
|
-
setFeedback("Exploratory capture command copied.");
|
|
1246
|
-
return;
|
|
1247
|
-
}
|
|
1248
|
-
const definition = {
|
|
1249
|
-
theme,
|
|
1250
|
-
camera: {
|
|
1251
|
-
type: "center",
|
|
1252
|
-
center: [roundNumber(camera.center[0]), roundNumber(camera.center[1])],
|
|
1253
|
-
zoom: roundNumber(camera.zoom),
|
|
1254
|
-
bearing: roundNumber(camera.bearing),
|
|
1255
|
-
pitch: roundNumber(camera.pitch)
|
|
1256
|
-
},
|
|
1257
|
-
viewport: {width, height, dpr},
|
|
1258
|
-
target: {kind: "map"}
|
|
1259
|
-
};
|
|
1260
|
-
const serialized = JSON.stringify(definition, null, 2).replace(/^/gm, " ");
|
|
1261
|
-
if (!await copyText("'" + name + "': " + serialized.trimStart() + ",")) {
|
|
1262
|
-
setFeedback("Clipboard access is unavailable.");
|
|
1263
|
-
return;
|
|
1264
|
-
}
|
|
1265
|
-
setFeedback("Scene entry copied.");
|
|
1266
|
-
}
|
|
1267
2739
|
|
|
1268
|
-
function readMapViewport(side, dpr) {
|
|
1269
|
-
const container = side.map?.getContainer?.();
|
|
1270
|
-
const canvas = side.map?.getCanvas?.();
|
|
1271
|
-
const maximum = dpr === 2 ? 2048 : 4096;
|
|
1272
|
-
const width = boundedInteger(container?.clientWidth || canvas?.clientWidth, 64, maximum, 1280);
|
|
1273
|
-
const height = boundedInteger(container?.clientHeight || canvas?.clientHeight, 64, maximum, 800);
|
|
1274
|
-
return {width, height};
|
|
1275
|
-
}
|
|
1276
2740
|
|
|
1277
|
-
async function copyText(value) {
|
|
1278
|
-
try {
|
|
1279
|
-
await navigator.clipboard.writeText(value);
|
|
1280
|
-
return true;
|
|
1281
|
-
} catch {
|
|
1282
|
-
const textarea = document.createElement("textarea");
|
|
1283
|
-
textarea.value = value;
|
|
1284
|
-
textarea.style.position = "fixed";
|
|
1285
|
-
textarea.style.opacity = "0";
|
|
1286
|
-
document.body.append(textarea);
|
|
1287
|
-
textarea.select();
|
|
1288
|
-
const copied = document.execCommand("copy");
|
|
1289
|
-
textarea.remove();
|
|
1290
|
-
return copied;
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
2741
|
|
|
1294
2742
|
function writeUrlState() {
|
|
1295
2743
|
const url = new URL(location.href);
|
|
1296
2744
|
url.searchParams.set("mode", stage.dataset.mode);
|
|
1297
|
-
url.searchParams.set("split", String(Math.round(
|
|
1298
|
-
url.searchParams.set("alpha", String(Math.round(
|
|
2745
|
+
url.searchParams.set("split", String(Math.round(initialUi.split)));
|
|
2746
|
+
url.searchParams.set("alpha", String(Math.round(initialUi.alpha)));
|
|
1299
2747
|
url.searchParams.set("driver", stage.dataset.driver);
|
|
1300
2748
|
if (canonicalCamera) {
|
|
1301
2749
|
const values = {
|
|
@@ -1310,9 +2758,9 @@ function renderTileflowComparisonHtml(options) {
|
|
|
1310
2758
|
function readUiFromUrl() {
|
|
1311
2759
|
const query = new URL(location.href).searchParams;
|
|
1312
2760
|
const modeValue = singleQueryValue(query, "mode");
|
|
1313
|
-
const mode = ["side-by-side", "split", "overlay", "blink"].includes(modeValue) ? modeValue : undefined;
|
|
2761
|
+
const mode = ["single", "side-by-side", "split", "overlay", "blink"].includes(modeValue) ? modeValue : undefined;
|
|
1314
2762
|
const driver = singleQueryValue(query, "driver");
|
|
1315
|
-
selectSide(driver === "right" ? "right" : "left", false);
|
|
2763
|
+
selectSide(driver === "right" && options.right ? "right" : "left", false);
|
|
1316
2764
|
const split = singleQueryValue(query, "split");
|
|
1317
2765
|
const alpha = singleQueryValue(query, "alpha");
|
|
1318
2766
|
return {
|
|
@@ -1414,7 +2862,6 @@ function renderTileflowComparisonHtml(options) {
|
|
|
1414
2862
|
if (!side.runtimeErrors.includes(message)) side.runtimeErrors.push(message);
|
|
1415
2863
|
side.runtimeErrors = side.runtimeErrors.slice(-16);
|
|
1416
2864
|
side.runtimeFailed = true;
|
|
1417
|
-
renderSideStatus(side);
|
|
1418
2865
|
updateDocumentReadiness();
|
|
1419
2866
|
}
|
|
1420
2867
|
|
|
@@ -1482,8 +2929,32 @@ function renderTileflowComparisonHtml(options) {
|
|
|
1482
2929
|
function roundNumber(value) { return Number(value.toFixed(6)); }
|
|
1483
2930
|
function formatNumber(value) { return String(roundNumber(value)); }
|
|
1484
2931
|
function wrapLongitude(value) { return ((value + 180) % 360 + 360) % 360 - 180; }
|
|
1485
|
-
function setFeedback(value) { feedback
|
|
2932
|
+
function setFeedback(value) { globalThis.__tileflowDevtoolsUI?.feedback(value); }
|
|
2933
|
+
${controlsClient}
|
|
2934
|
+
${workbenchClient}
|
|
2935
|
+
globalThis.__tileflowDevtoolsHost = {
|
|
2936
|
+
schemaVersion: 1,
|
|
2937
|
+
subscribePreviewStatus: (child, receive) => {
|
|
2938
|
+
const side = Object.values(states).find((entry) => entry.frame.contentWindow === child);
|
|
2939
|
+
if (!side) return undefined;
|
|
2940
|
+
side.previewStatusReceiver = receive;
|
|
2941
|
+
if (side.latestArtifactStatus) receive(side.latestArtifactStatus);
|
|
2942
|
+
return () => { if (side.previewStatusReceiver === receive) side.previewStatusReceiver = undefined; };
|
|
2943
|
+
},
|
|
2944
|
+
sides: () => Object.values(states), active: () => states[activeSideId],
|
|
2945
|
+
selection: () => selectedInspection,
|
|
2946
|
+
clearFeatureSelection: () => {
|
|
2947
|
+
selectedInspection = undefined;
|
|
2948
|
+
for (const side of Object.values(states)) { side.highlightFeature?.(-1); side.hoverFeature?.(-1); }
|
|
2949
|
+
},
|
|
2950
|
+
loadSidecar: (side) => loadSidecar(side),
|
|
2951
|
+
selectFeature: (index) => { if (selectedInspection && selectedInspection.features[index]) { selectedInspection.featureIndex = index; renderInspection(true); } },
|
|
2952
|
+
inspect: (enabled) => setInspectionEnabled(enabled), inspecting: () => inspectionEnabled,
|
|
2953
|
+
controls: () => loadMapControls(),
|
|
2954
|
+
sprites: () => loadSprites(states[activeSideId])
|
|
2955
|
+
};
|
|
1486
2956
|
</script>
|
|
2957
|
+
<script type="module"${nonceAttribute} src="${fontBasePath}/__workbench-runtime/devtools.js"></script>
|
|
1487
2958
|
</body>
|
|
1488
2959
|
</html>`;
|
|
1489
2960
|
}
|
|
@@ -1515,15 +2986,56 @@ var tileflowComparisonSchemaVersion = 1;
|
|
|
1515
2986
|
function createTileflowComparisonRequestHandler(options) {
|
|
1516
2987
|
const comparisonBasePath = normalizeTileflowBasePath(options.basePath);
|
|
1517
2988
|
const left = normalizeSide(options.left, "left", comparisonBasePath);
|
|
1518
|
-
const right = normalizeSide(options.right, "right", comparisonBasePath);
|
|
1519
|
-
assertDisjointSideRoutes(left.basePath, right.basePath);
|
|
1520
|
-
const
|
|
1521
|
-
|
|
2989
|
+
const right = options.right ? normalizeSide(options.right, "right", comparisonBasePath) : void 0;
|
|
2990
|
+
if (right) assertDisjointSideRoutes(left.basePath, right.basePath);
|
|
2991
|
+
const workbenchUrl = options.workbench ? normalizeTileflowBasePath(options.workbench.url) : void 0;
|
|
2992
|
+
if (workbenchUrl !== void 0) {
|
|
2993
|
+
if (!workbenchUrl || comparisonBasePath && !matchesBasePath(workbenchUrl, comparisonBasePath))
|
|
2994
|
+
throw new TypeError(
|
|
2995
|
+
"Map DevTools API must own a non-root route inside the comparison base path."
|
|
2996
|
+
);
|
|
2997
|
+
assertDisjointSideRoutes(workbenchUrl, left.basePath);
|
|
2998
|
+
if (right) assertDisjointSideRoutes(workbenchUrl, right.basePath);
|
|
2999
|
+
assertDisjointSideRoutes(workbenchUrl, `${comparisonBasePath}/__workbench-fonts`);
|
|
3000
|
+
assertDisjointSideRoutes(workbenchUrl, `${comparisonBasePath}/__workbench-runtime`);
|
|
3001
|
+
}
|
|
3002
|
+
const title = normalizeLabel(options.title ?? "Tileflow Map DevTools", "title");
|
|
3003
|
+
const initialMode = right ? options.initialMode ?? "side-by-side" : "single";
|
|
1522
3004
|
return async function handleTileflowComparisonRequest(request) {
|
|
1523
3005
|
const url = new URL(request.url);
|
|
1524
3006
|
const pathname = url.pathname;
|
|
3007
|
+
if (pathname === `${comparisonBasePath}/__workbench-runtime/devtools.js`) {
|
|
3008
|
+
if (request.method !== "GET" && request.method !== "HEAD")
|
|
3009
|
+
return jsonResponse({ error: "Method not allowed" }, 405);
|
|
3010
|
+
const source = await readFile(
|
|
3011
|
+
new URL("../dist/devtools-browser.js", import.meta.url),
|
|
3012
|
+
"utf8"
|
|
3013
|
+
);
|
|
3014
|
+
return new Response(request.method === "HEAD" ? null : source, {
|
|
3015
|
+
headers: {
|
|
3016
|
+
"Content-Type": "text/javascript; charset=utf-8",
|
|
3017
|
+
"Cache-Control": "no-store",
|
|
3018
|
+
"X-Content-Type-Options": "nosniff"
|
|
3019
|
+
}
|
|
3020
|
+
});
|
|
3021
|
+
}
|
|
3022
|
+
const font = pathname.slice(`${comparisonBasePath}/__workbench-fonts/`.length);
|
|
3023
|
+
if (pathname.startsWith(`${comparisonBasePath}/__workbench-fonts/`) && ["mona-sans.woff2", "hubot-sans.woff2", "inter.woff2", "jetbrains-mono.woff2"].includes(font)) {
|
|
3024
|
+
if (request.method !== "GET" && request.method !== "HEAD")
|
|
3025
|
+
return jsonResponse({ error: "Method not allowed" }, 405);
|
|
3026
|
+
const bytes = await readFile(new URL(`../ui-fonts/${font}`, import.meta.url));
|
|
3027
|
+
return new Response(request.method === "HEAD" ? null : bytes, {
|
|
3028
|
+
headers: {
|
|
3029
|
+
"Content-Type": "font/woff2",
|
|
3030
|
+
"Cache-Control": "public, max-age=3600",
|
|
3031
|
+
"X-Content-Type-Options": "nosniff"
|
|
3032
|
+
}
|
|
3033
|
+
});
|
|
3034
|
+
}
|
|
3035
|
+
if (options.workbench && workbenchUrl && matchesBasePath(pathname, workbenchUrl))
|
|
3036
|
+
return await options.workbench.handle(request);
|
|
1525
3037
|
if (matchesBasePath(pathname, left.basePath)) return await left.handler(request);
|
|
1526
|
-
if (matchesBasePath(pathname, right.basePath)) return await right.handler(request);
|
|
3038
|
+
if (right && matchesBasePath(pathname, right.basePath)) return await right.handler(request);
|
|
1527
3039
|
if (isComparisonRoot(pathname, comparisonBasePath)) {
|
|
1528
3040
|
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
1529
3041
|
return jsonResponse({ error: "Method not allowed" }, 405);
|
|
@@ -1534,9 +3046,10 @@ function createTileflowComparisonRequestHandler(options) {
|
|
|
1534
3046
|
cspNonce,
|
|
1535
3047
|
initialMode,
|
|
1536
3048
|
left: publicSide(left),
|
|
1537
|
-
right: publicSide(right),
|
|
3049
|
+
...right ? { right: publicSide(right) } : {},
|
|
1538
3050
|
schemaVersion: tileflowComparisonSchemaVersion,
|
|
1539
|
-
title
|
|
3051
|
+
title,
|
|
3052
|
+
...workbenchUrl ? { workbenchUrl } : {}
|
|
1540
3053
|
});
|
|
1541
3054
|
return new Response(request.method === "HEAD" ? null : html, {
|
|
1542
3055
|
headers: {
|
|
@@ -1572,6 +3085,7 @@ function normalizeSide(side, name, comparisonBasePath) {
|
|
|
1572
3085
|
if (!basePath) {
|
|
1573
3086
|
throw new TypeError(`Tileflow comparison ${name} basePath must not own the server root.`);
|
|
1574
3087
|
}
|
|
3088
|
+
assertDisjointSideRoutes(basePath, `${comparisonBasePath}/__workbench-runtime`);
|
|
1575
3089
|
if (comparisonBasePath && !matchesBasePath(basePath, comparisonBasePath)) {
|
|
1576
3090
|
throw new TypeError(
|
|
1577
3091
|
`Tileflow comparison ${name} basePath must be inside ${comparisonBasePath}.`
|
|
@@ -1724,6 +3238,8 @@ function createTileflowDevRequestHandler(options = {}) {
|
|
|
1724
3238
|
const lineBackgroundGlyphs = createTileflowLineBackgroundGlyphResponder({ cwd });
|
|
1725
3239
|
let closed = false;
|
|
1726
3240
|
let lastOrigin;
|
|
3241
|
+
let controlsGeneration;
|
|
3242
|
+
const controlsCache = /* @__PURE__ */ new Map();
|
|
1727
3243
|
const loadOwnedSession = (origin) => {
|
|
1728
3244
|
lastOrigin = origin;
|
|
1729
3245
|
ownedSessionPromise ??= createTileflowArtifactSession({
|
|
@@ -1755,13 +3271,30 @@ function createTileflowDevRequestHandler(options = {}) {
|
|
|
1755
3271
|
return options.session ? eventStreamResponse(options.session) : jsonResponse2({ error: "Live events require a development session." }, 404);
|
|
1756
3272
|
}
|
|
1757
3273
|
if (path.startsWith("/__runtime/")) {
|
|
1758
|
-
const runtimeResponse = (await import("./preview-assets-
|
|
3274
|
+
const runtimeResponse = (await import("./preview-assets-KGUICQHG.js")).getTileflowPreviewRuntimeResponse(path);
|
|
1759
3275
|
if (runtimeResponse) return runtimeResponse;
|
|
1760
3276
|
}
|
|
1761
3277
|
const session = options.session ?? await loadOwnedSession(url.origin);
|
|
1762
3278
|
acquisition = session.acquireArtifacts();
|
|
1763
3279
|
const artifacts = acquisition?.artifacts;
|
|
1764
3280
|
const state = session.getState();
|
|
3281
|
+
const controls = url.searchParams.get("controls");
|
|
3282
|
+
if (controls !== null) parsePreviewControls(controls);
|
|
3283
|
+
const controlled = (map, theme) => {
|
|
3284
|
+
if (!artifacts || controls === null) return void 0;
|
|
3285
|
+
if (controlsGeneration !== acquisition?.generation) {
|
|
3286
|
+
controlsCache.clear();
|
|
3287
|
+
controlsGeneration = acquisition?.generation;
|
|
3288
|
+
}
|
|
3289
|
+
const key = JSON.stringify([map, theme, controls]);
|
|
3290
|
+
let value = controlsCache.get(key);
|
|
3291
|
+
if (!value) {
|
|
3292
|
+
value = compilePreviewControls(artifacts, map, theme, controls, apiBaseUrl);
|
|
3293
|
+
if (controlsCache.size >= 8) controlsCache.delete(controlsCache.keys().next().value);
|
|
3294
|
+
controlsCache.set(key, value);
|
|
3295
|
+
}
|
|
3296
|
+
return value;
|
|
3297
|
+
};
|
|
1765
3298
|
if (path === "/__status") {
|
|
1766
3299
|
return jsonResponse2(createTileflowArtifactStatus(state));
|
|
1767
3300
|
}
|
|
@@ -1769,20 +3302,24 @@ function createTileflowDevRequestHandler(options = {}) {
|
|
|
1769
3302
|
const { renderTileflowPreviewHtml, resolveTileflowPreview } = await import("./preview.js");
|
|
1770
3303
|
const requestedMaps = url.searchParams.getAll("map");
|
|
1771
3304
|
const requestedThemes = url.searchParams.getAll("theme");
|
|
3305
|
+
const requestedScenes = url.searchParams.getAll("scene");
|
|
3306
|
+
if (requestedScenes.length > 1) {
|
|
3307
|
+
return jsonResponse2({ error: "Tileflow scene query must appear at most once." }, 400);
|
|
3308
|
+
}
|
|
1772
3309
|
if (requestedMaps.length > 1) {
|
|
1773
3310
|
return jsonResponse2({ error: "Tileflow map query must appear at most once." }, 400);
|
|
1774
3311
|
}
|
|
1775
3312
|
if (requestedThemes.length > 1) {
|
|
1776
3313
|
return jsonResponse2({ error: "Tileflow theme query must appear at most once." }, 400);
|
|
1777
3314
|
}
|
|
1778
|
-
const requestedMap = requestedMaps.length
|
|
1779
|
-
const requestedTheme = requestedThemes.length
|
|
3315
|
+
const requestedMap = requestedMaps[0] ?? (requestedScenes.length ? void 0 : options.map);
|
|
3316
|
+
const requestedTheme = requestedThemes[0] ?? (requestedScenes.length ? void 0 : options.theme);
|
|
1780
3317
|
const preview = artifacts ? resolveTileflowPreview(artifacts.project, {
|
|
1781
3318
|
map: requestedMap,
|
|
1782
|
-
scene: options.scene,
|
|
3319
|
+
scene: requestedScenes[0] ?? (requestedMaps.length ? void 0 : options.scene),
|
|
1783
3320
|
theme: requestedTheme
|
|
1784
3321
|
}) : void 0;
|
|
1785
|
-
const previewStyle = preview ? artifacts?.styles[preview.mapName]?.[preview.themeName] : void 0;
|
|
3322
|
+
const previewStyle = preview ? controlled(preview.mapName, preview.themeName)?.style ?? artifacts?.styles[preview.mapName]?.[preview.themeName] : void 0;
|
|
1786
3323
|
const isSemanticPreview = preview !== void 0 && previewStyle?.metadata?.["tileflow:compiler"] === "tileflow-semantic";
|
|
1787
3324
|
const fontFaces = previewStyle ? getTileflowStyleFontFaces(previewStyle) : [];
|
|
1788
3325
|
return htmlResponse(
|
|
@@ -1791,11 +3328,23 @@ function createTileflowDevRequestHandler(options = {}) {
|
|
|
1791
3328
|
basePath,
|
|
1792
3329
|
createTileflowArtifactStatus(state),
|
|
1793
3330
|
isSemanticPreview,
|
|
1794
|
-
fontFaces
|
|
3331
|
+
fontFaces,
|
|
3332
|
+
options.fitViewport,
|
|
3333
|
+
controls ?? void 0
|
|
1795
3334
|
)
|
|
1796
3335
|
);
|
|
1797
3336
|
}
|
|
1798
3337
|
if (!artifacts) return unavailableArtifactsResponse(state);
|
|
3338
|
+
if (path === "/__controls") {
|
|
3339
|
+
return compactJsonResponse({
|
|
3340
|
+
...describePreviewControls(
|
|
3341
|
+
artifacts,
|
|
3342
|
+
url.searchParams.get("map") ?? "",
|
|
3343
|
+
url.searchParams.get("theme") ?? ""
|
|
3344
|
+
),
|
|
3345
|
+
generation: acquisition?.generation
|
|
3346
|
+
});
|
|
3347
|
+
}
|
|
1799
3348
|
if (path.startsWith(`/${tileflowLineBackgroundGlyphRoute}/`)) {
|
|
1800
3349
|
const glyphRequest = parseTileflowLineBackgroundGlyphPath(path);
|
|
1801
3350
|
const upstream = glyphRequest ? artifacts.lineBackgroundGlyphs?.[glyphRequest.mapName] : void 0;
|
|
@@ -1824,7 +3373,7 @@ function createTileflowDevRequestHandler(options = {}) {
|
|
|
1824
3373
|
}
|
|
1825
3374
|
if (path.startsWith("/__inspection/")) {
|
|
1826
3375
|
const selection = getTileflowStyleInspectionSelection(path);
|
|
1827
|
-
const inspection = selection ? artifacts.styleInspections?.[selection.mapName]?.[selection.themeName] : void 0;
|
|
3376
|
+
const inspection = selection ? controlled(selection.mapName, selection.themeName)?.inspection ?? artifacts.styleInspections?.[selection.mapName]?.[selection.themeName] : void 0;
|
|
1828
3377
|
if (!selection || !inspection) {
|
|
1829
3378
|
return jsonResponse2({ error: "Compiler inspection is unavailable." }, 404);
|
|
1830
3379
|
}
|
|
@@ -1832,7 +3381,7 @@ function createTileflowDevRequestHandler(options = {}) {
|
|
|
1832
3381
|
}
|
|
1833
3382
|
if (path.startsWith("/styles/")) {
|
|
1834
3383
|
const selection = getTileflowStyleSelection(path);
|
|
1835
|
-
const style = selection ? artifacts.styles[selection.mapName]?.[selection.themeName] : void 0;
|
|
3384
|
+
const style = selection ? controlled(selection.mapName, selection.themeName)?.style ?? artifacts.styles[selection.mapName]?.[selection.themeName] : void 0;
|
|
1836
3385
|
if (!selection || !style) {
|
|
1837
3386
|
return jsonResponse2({ error: "Unknown map theme style." }, 404);
|
|
1838
3387
|
}
|
|
@@ -1854,9 +3403,23 @@ function createTileflowDevRequestHandler(options = {}) {
|
|
|
1854
3403
|
}
|
|
1855
3404
|
return assetResponse(asset);
|
|
1856
3405
|
}
|
|
3406
|
+
if (path.startsWith("/models/")) {
|
|
3407
|
+
const assetName = path.replace(/^\/+/, "");
|
|
3408
|
+
const asset = artifacts.assets.find((candidate) => candidate.fileName === assetName);
|
|
3409
|
+
if (!asset) {
|
|
3410
|
+
return jsonResponse2({ error: `Unknown model asset: ${assetName}` }, 404);
|
|
3411
|
+
}
|
|
3412
|
+
return assetResponse(asset);
|
|
3413
|
+
}
|
|
1857
3414
|
return jsonResponse2({ error: "Not found" }, 404);
|
|
1858
3415
|
} catch (error) {
|
|
1859
3416
|
options.onError?.(error);
|
|
3417
|
+
if (url.searchParams.has("controls") || path === "/__controls") {
|
|
3418
|
+
return jsonResponse2(
|
|
3419
|
+
{ error: error instanceof Error ? error.message : "Invalid map controls." },
|
|
3420
|
+
400
|
|
3421
|
+
);
|
|
3422
|
+
}
|
|
1860
3423
|
return tileflowErrorResponse(error);
|
|
1861
3424
|
} finally {
|
|
1862
3425
|
await acquisition?.release();
|
|
@@ -2004,7 +3567,7 @@ function getRequestPathname(url) {
|
|
|
2004
3567
|
}
|
|
2005
3568
|
}
|
|
2006
3569
|
function isOwnedTileflowRequestPath(path) {
|
|
2007
|
-
return path === "/" || path === "/build-manifest.json" || path === "/manifest.json" || path === "/__events" || path === "/__status" || getTileflowStyleInspectionSelection(path) !== void 0 || path.startsWith("/generations/") || path.startsWith("/icons/") || path.startsWith("/tilesets/") || getTileflowStyleSelection(path) !== void 0 || path.startsWith("/fonts/") || path.startsWith(`/${tileflowLineBackgroundGlyphRoute}/`) || path.startsWith("/__runtime/");
|
|
3570
|
+
return path === "/" || path === "/build-manifest.json" || path === "/manifest.json" || path === "/__events" || path === "/__status" || path === "/__controls" || getTileflowStyleInspectionSelection(path) !== void 0 || path.startsWith("/generations/") || path.startsWith("/icons/") || path.startsWith("/tilesets/") || getTileflowStyleSelection(path) !== void 0 || path.startsWith("/fonts/") || path.startsWith("/models/") || path.startsWith(`/${tileflowLineBackgroundGlyphRoute}/`) || path.startsWith("/__runtime/");
|
|
2008
3571
|
}
|
|
2009
3572
|
function tileflowErrorResponse(error) {
|
|
2010
3573
|
if (error instanceof Error && "code" in error && error.code === "PREVIEW_SELECTION_INVALID") {
|