@fps-games/editor 0.1.0 → 0.1.1-beta.0
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/dist/local-editor-harness.d.ts +70 -26
- package/dist/local-editor-harness.d.ts.map +1 -1
- package/dist/local-editor-harness.js +455 -21
- package/dist/local-editor-harness.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/package.json +4 -4
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-context-menu.d.ts +15 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-context-menu.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-context-menu.js +163 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-context-menu.js.map +1 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.d.ts +28 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.js +164 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.js.map +1 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.d.ts +20 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.js +483 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.js.map +1 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-tree.d.ts +56 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-tree.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-tree.js +216 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-tree.js.map +1 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-view.d.ts +13 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-view.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-view.js +138 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-view.js.map +1 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-input-router.d.ts +3 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-input-router.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-input-router.js +29 -5
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-input-router.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.d.ts +10 -2
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.js +554 -93
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-primitives.d.ts +2 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-primitives.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-primitives.js +7 -3
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-primitives.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shortcuts.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shortcuts.js +2 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shortcuts.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts +148 -2
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.d.ts +11 -2
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.js +206 -173
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/package.json +1 -1
- package/node_modules/@fps-games/editor-core/dist/index.d.ts +2 -0
- package/node_modules/@fps-games/editor-core/dist/index.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-core/dist/index.js +2 -0
- package/node_modules/@fps-games/editor-core/dist/index.js.map +1 -1
- package/node_modules/@fps-games/editor-core/dist/inspector.d.ts +138 -0
- package/node_modules/@fps-games/editor-core/dist/inspector.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-core/dist/inspector.js +298 -0
- package/node_modules/@fps-games/editor-core/dist/inspector.js.map +1 -0
- package/node_modules/@fps-games/editor-core/dist/scene-graph.d.ts +28 -0
- package/node_modules/@fps-games/editor-core/dist/scene-graph.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-core/dist/scene-graph.js +143 -2
- package/node_modules/@fps-games/editor-core/dist/scene-graph.js.map +1 -1
- package/node_modules/@fps-games/editor-core/dist/transform-math.d.ts +30 -0
- package/node_modules/@fps-games/editor-core/dist/transform-math.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-core/dist/transform-math.js +339 -0
- package/node_modules/@fps-games/editor-core/dist/transform-math.js.map +1 -0
- package/node_modules/@fps-games/editor-core/package.json +2 -2
- package/node_modules/@fps-games/editor-forge-play/dist/index.js.map +1 -1
- package/node_modules/@fps-games/editor-forge-play/package.json +2 -2
- package/node_modules/@fps-games/editor-protocol/package.json +1 -1
- package/package.json +11 -6
|
@@ -258,127 +258,185 @@ function formatHistoryTimestamp(createdAt) {
|
|
|
258
258
|
pad(date.getSeconds()),
|
|
259
259
|
].join('');
|
|
260
260
|
}
|
|
261
|
-
export function renderInspectorPanel(doc, panel, state) {
|
|
261
|
+
export function renderInspectorPanel(doc, panel, state, filter = '', options = {}) {
|
|
262
262
|
clearElement(panel);
|
|
263
263
|
const title = doc.createElement('h2');
|
|
264
264
|
title.textContent = 'Inspector';
|
|
265
265
|
title.style.cssText = 'font-size:13px;margin:-8px -8px 8px;padding:7px 8px;border-bottom:1px solid var(--fps-editor-divider);background:var(--fps-editor-chrome-dark);font-weight:800;color:var(--fps-editor-text-strong)';
|
|
266
266
|
panel.appendChild(title);
|
|
267
|
+
const search = createInspectorSearchInput(doc, filter);
|
|
268
|
+
panel.appendChild(search);
|
|
267
269
|
const selectionCount = state.selectionSummary?.count ?? state.selectedIds.length;
|
|
268
|
-
|
|
269
|
-
|
|
270
|
+
const inspectorObject = selectionCount > 1
|
|
271
|
+
? state.inspectorMultiObject
|
|
272
|
+
?? (state.serializedMultiObject ? createLegacyInspectorObject(state.serializedMultiObject) : createSelectionSummaryInspectorObject(state))
|
|
273
|
+
: state.inspectorObject ?? (state.serializedObject ? createLegacyInspectorObject(state.serializedObject) : null);
|
|
274
|
+
if (!inspectorObject) {
|
|
275
|
+
const empty = doc.createElement('div');
|
|
276
|
+
empty.textContent = '请从层级树或 Scene View 中选择一个 GameObject。';
|
|
277
|
+
empty.style.cssText = 'color:var(--fps-editor-muted);line-height:1.45';
|
|
278
|
+
panel.appendChild(empty);
|
|
270
279
|
return;
|
|
271
280
|
}
|
|
272
|
-
const
|
|
273
|
-
|
|
281
|
+
const controlRegistry = createLocalEditorBrowserInspectorControlRegistry(options.controls, options.controlConflict);
|
|
282
|
+
appendInspectorSummary(doc, panel, inspectorObject, selectionCount);
|
|
283
|
+
const visibleSections = filterInspectorSections(inspectorObject.sections.filter(section => section.placement !== 'summary'), filter);
|
|
284
|
+
if (visibleSections.length === 0) {
|
|
285
|
+
if (!filter.trim() && inspectorObject.sections.length === 0)
|
|
286
|
+
return;
|
|
274
287
|
const empty = doc.createElement('div');
|
|
275
|
-
empty.textContent = '
|
|
288
|
+
empty.textContent = '没有匹配的 Inspector 字段。';
|
|
276
289
|
empty.style.cssText = 'color:var(--fps-editor-muted);line-height:1.45';
|
|
277
290
|
panel.appendChild(empty);
|
|
278
291
|
return;
|
|
279
292
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
for (const section of sections) {
|
|
283
|
-
const block = createInspectorComponentBlock(doc);
|
|
284
|
-
const sectionTitle = doc.createElement('h3');
|
|
285
|
-
sectionTitle.textContent = section.title;
|
|
286
|
-
sectionTitle.style.cssText = 'font-size:12px;margin:0 0 8px;font-weight:900;color:var(--fps-editor-text-strong)';
|
|
287
|
-
block.appendChild(sectionTitle);
|
|
288
|
-
for (const group of section.groups) {
|
|
289
|
-
if (group.kind === 'vec3')
|
|
290
|
-
appendSerializedVec3Inputs(doc, block, serializedObject, group.label, group.properties);
|
|
291
|
-
else
|
|
292
|
-
appendSerializedPropertyRow(doc, block, serializedObject, group.property);
|
|
293
|
-
}
|
|
294
|
-
panel.appendChild(block);
|
|
293
|
+
for (const section of visibleSections) {
|
|
294
|
+
panel.appendChild(createInspectorSectionBlock(doc, inspectorObject, section, controlRegistry));
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
|
-
function
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
for (const group of section.groups) {
|
|
317
|
-
if (group.kind === 'vec3')
|
|
318
|
-
appendSerializedVec3Inputs(doc, sectionBlock, serializedMultiObject, group.label, group.properties);
|
|
319
|
-
else
|
|
320
|
-
appendSerializedPropertyRow(doc, sectionBlock, serializedMultiObject, group.property);
|
|
321
|
-
}
|
|
322
|
-
panel.appendChild(sectionBlock);
|
|
323
|
-
}
|
|
297
|
+
function createInspectorSearchInput(doc, value) {
|
|
298
|
+
const input = doc.createElement('input');
|
|
299
|
+
input.dataset.editorInspectorSearch = 'true';
|
|
300
|
+
input.value = value;
|
|
301
|
+
input.placeholder = 'Search...';
|
|
302
|
+
input.style.cssText = [
|
|
303
|
+
'width:100%',
|
|
304
|
+
'height:30px',
|
|
305
|
+
'box-sizing:border-box',
|
|
306
|
+
'margin:0 0 8px',
|
|
307
|
+
'border:1px solid var(--fps-editor-border)',
|
|
308
|
+
'border-radius:3px',
|
|
309
|
+
'background:var(--fps-editor-field)',
|
|
310
|
+
'color:var(--fps-editor-text)',
|
|
311
|
+
'font-size:12px',
|
|
312
|
+
'padding:0 8px',
|
|
313
|
+
'outline:none',
|
|
314
|
+
].join(';');
|
|
315
|
+
return input;
|
|
324
316
|
}
|
|
325
|
-
function
|
|
317
|
+
function appendInspectorSummary(doc, panel, inspectorObject, selectionCount) {
|
|
326
318
|
const block = createInspectorComponentBlock(doc);
|
|
327
319
|
const title = doc.createElement('h3');
|
|
328
|
-
title.textContent = 'GameObject';
|
|
320
|
+
title.textContent = selectionCount > 1 ? 'Selection' : 'GameObject';
|
|
329
321
|
title.style.cssText = 'font-size:12px;margin:0 0 8px;font-weight:900;color:var(--fps-editor-text-strong)';
|
|
330
322
|
block.appendChild(title);
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
323
|
+
appendReadOnlyRow(doc, block, selectionCount > 1 ? 'Selected' : 'Name', inspectorObject.label ?? inspectorObject.activeId ?? inspectorObject.targetIds[0] ?? '无');
|
|
324
|
+
appendReadOnlyRow(doc, block, 'Active ID', inspectorObject.activeId ?? '无');
|
|
325
|
+
if (selectionCount > 1)
|
|
326
|
+
appendReadOnlyRow(doc, block, 'Count', String(selectionCount));
|
|
335
327
|
panel.appendChild(block);
|
|
336
328
|
}
|
|
329
|
+
function filterInspectorSections(sections, filter) {
|
|
330
|
+
const needle = filter.trim().toLowerCase();
|
|
331
|
+
if (!needle)
|
|
332
|
+
return sections;
|
|
333
|
+
return sections
|
|
334
|
+
.map(section => ({
|
|
335
|
+
...section,
|
|
336
|
+
properties: section.properties.filter(property => inspectorPropertyMatches(section, property, needle)),
|
|
337
|
+
}))
|
|
338
|
+
.filter(section => section.properties.length > 0 || inspectorSectionMatches(section, needle));
|
|
339
|
+
}
|
|
340
|
+
function inspectorSectionMatches(section, needle) {
|
|
341
|
+
return [section.id, section.title, section.summary ?? '', ...(section.tags ?? [])]
|
|
342
|
+
.some(value => value.toLowerCase().includes(needle));
|
|
343
|
+
}
|
|
344
|
+
function inspectorPropertyMatches(section, property, needle) {
|
|
345
|
+
return inspectorSectionMatches(section, needle)
|
|
346
|
+
|| [property.path, property.label, property.control, property.valueType, ...(property.tags ?? [])]
|
|
347
|
+
.some(value => String(value).toLowerCase().includes(needle));
|
|
348
|
+
}
|
|
349
|
+
function createInspectorSectionBlock(doc, inspectorObject, section, controlRegistry) {
|
|
350
|
+
const block = doc.createElement('details');
|
|
351
|
+
block.dataset.editorInspectorSection = section.id;
|
|
352
|
+
block.open = section.collapsedByDefault !== true;
|
|
353
|
+
block.style.cssText = createInspectorBlockStyle();
|
|
354
|
+
const sectionTitle = doc.createElement('summary');
|
|
355
|
+
sectionTitle.style.cssText = [
|
|
356
|
+
'font-size:12px',
|
|
357
|
+
'margin:-2px 0 8px',
|
|
358
|
+
'font-weight:900',
|
|
359
|
+
'color:var(--fps-editor-text-strong)',
|
|
360
|
+
'display:flex',
|
|
361
|
+
'align-items:center',
|
|
362
|
+
'justify-content:space-between',
|
|
363
|
+
'gap:8px',
|
|
364
|
+
'cursor:pointer',
|
|
365
|
+
'list-style:none',
|
|
366
|
+
].join(';');
|
|
367
|
+
const label = doc.createElement('span');
|
|
368
|
+
label.textContent = section.title;
|
|
369
|
+
sectionTitle.appendChild(label);
|
|
370
|
+
const meta = doc.createElement('span');
|
|
371
|
+
meta.style.cssText = 'display:flex;align-items:center;gap:6px;min-width:0;color:var(--fps-editor-muted);font-size:10px;text-transform:uppercase;letter-spacing:0';
|
|
372
|
+
if (section.summary) {
|
|
373
|
+
const summary = doc.createElement('span');
|
|
374
|
+
summary.textContent = section.summary;
|
|
375
|
+
summary.style.cssText = 'overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-transform:none;font-size:11px';
|
|
376
|
+
meta.appendChild(summary);
|
|
377
|
+
}
|
|
378
|
+
if (section.persistence === 'runtime' || section.runtimeOnly || section.persistence === 'readonly') {
|
|
379
|
+
const badge = doc.createElement('span');
|
|
380
|
+
badge.textContent = section.persistence === 'runtime' || section.runtimeOnly ? 'Runtime' : 'Readonly';
|
|
381
|
+
badge.style.cssText = 'flex:0 0 auto';
|
|
382
|
+
meta.appendChild(badge);
|
|
383
|
+
}
|
|
384
|
+
sectionTitle.appendChild(meta);
|
|
385
|
+
block.appendChild(sectionTitle);
|
|
386
|
+
const content = doc.createElement('div');
|
|
387
|
+
content.dataset.editorInspectorSectionContent = section.id;
|
|
388
|
+
for (const group of groupInspectorProperties(section.properties)) {
|
|
389
|
+
if (group.kind === 'vector')
|
|
390
|
+
appendInspectorVectorInputs(doc, content, inspectorObject, group.label, group.properties);
|
|
391
|
+
else
|
|
392
|
+
appendInspectorPropertyRow(doc, content, inspectorObject, group.property, controlRegistry);
|
|
393
|
+
}
|
|
394
|
+
block.appendChild(content);
|
|
395
|
+
return block;
|
|
396
|
+
}
|
|
337
397
|
function createInspectorComponentBlock(doc) {
|
|
338
398
|
const block = doc.createElement('div');
|
|
339
|
-
block.style.cssText =
|
|
399
|
+
block.style.cssText = createInspectorBlockStyle();
|
|
400
|
+
return block;
|
|
401
|
+
}
|
|
402
|
+
function createInspectorBlockStyle() {
|
|
403
|
+
return [
|
|
340
404
|
'margin:0 0 8px',
|
|
341
405
|
'padding:9px',
|
|
342
406
|
'border:1px solid var(--fps-editor-border)',
|
|
343
407
|
'border-radius:3px',
|
|
344
408
|
'background:var(--fps-editor-panel-soft)',
|
|
345
409
|
].join(';');
|
|
346
|
-
return block;
|
|
347
410
|
}
|
|
348
|
-
function
|
|
349
|
-
const bySection = new Map();
|
|
350
|
-
for (const property of properties) {
|
|
351
|
-
const sectionName = toTitle(property.path.split('.')[0] ?? 'Properties');
|
|
352
|
-
const entries = bySection.get(sectionName) ?? [];
|
|
353
|
-
entries.push(property);
|
|
354
|
-
bySection.set(sectionName, entries);
|
|
355
|
-
}
|
|
356
|
-
return [...bySection.entries()].map(([title, entries]) => ({
|
|
357
|
-
title,
|
|
358
|
-
groups: groupVectorProperties(entries),
|
|
359
|
-
}));
|
|
360
|
-
}
|
|
361
|
-
function groupVectorProperties(properties) {
|
|
411
|
+
function groupInspectorProperties(properties) {
|
|
362
412
|
const groups = [];
|
|
363
413
|
const consumed = new Set();
|
|
364
414
|
for (const property of properties) {
|
|
365
415
|
if (consumed.has(property.path))
|
|
366
416
|
continue;
|
|
367
|
-
|
|
417
|
+
if (property.control === 'vec2' || property.control === 'vec3') {
|
|
418
|
+
groups.push({ kind: 'property', property });
|
|
419
|
+
consumed.add(property.path);
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
const match = property.path.match(/^(.*)\.(x|y|z|r|g|b)$/);
|
|
368
423
|
if (!match || property.valueType !== 'number') {
|
|
369
424
|
groups.push({ kind: 'property', property });
|
|
370
425
|
consumed.add(property.path);
|
|
371
426
|
continue;
|
|
372
427
|
}
|
|
373
428
|
const basePath = match[1];
|
|
374
|
-
const
|
|
429
|
+
const axes = ['x', 'y', 'z'].every(axis => properties.some(candidate => candidate.path === `${basePath}.${axis}`))
|
|
430
|
+
? ['x', 'y', 'z']
|
|
431
|
+
: ['r', 'g', 'b'];
|
|
432
|
+
const vector = axes
|
|
375
433
|
.map(axis => properties.find(candidate => candidate.path === `${basePath}.${axis}` && candidate.valueType === 'number'))
|
|
376
434
|
.filter((candidate) => !!candidate);
|
|
377
435
|
if (vector.length === 3) {
|
|
378
436
|
for (const entry of vector)
|
|
379
437
|
consumed.add(entry.path);
|
|
380
438
|
const parts = basePath.split('.');
|
|
381
|
-
groups.push({ kind: '
|
|
439
|
+
groups.push({ kind: 'vector', label: toTitle(parts[parts.length - 1] ?? basePath), properties: vector });
|
|
382
440
|
}
|
|
383
441
|
else {
|
|
384
442
|
groups.push({ kind: 'property', property });
|
|
@@ -387,15 +445,135 @@ function groupVectorProperties(properties) {
|
|
|
387
445
|
}
|
|
388
446
|
return groups;
|
|
389
447
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
448
|
+
const builtinInspectorControlRegistrations = [
|
|
449
|
+
{
|
|
450
|
+
id: 'builtin.readonly',
|
|
451
|
+
order: 100,
|
|
452
|
+
control: 'readonly',
|
|
453
|
+
render: ({ doc, target, property }) => createInspectorReadonlyControl(doc, target, property),
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
id: 'builtin.string',
|
|
457
|
+
order: 100,
|
|
458
|
+
control: 'string',
|
|
459
|
+
render: ({ doc, target, property }) => createInspectorTextControl(doc, target, property),
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
id: 'builtin.number',
|
|
463
|
+
order: 100,
|
|
464
|
+
control: 'number',
|
|
465
|
+
render: ({ doc, target, property }) => createInspectorNumberControl(doc, target, property),
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
id: 'builtin.boolean',
|
|
469
|
+
order: 100,
|
|
470
|
+
control: 'boolean',
|
|
471
|
+
render: ({ doc, target, property }) => createInspectorBooleanControl(doc, target, property),
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
id: 'builtin.enum',
|
|
475
|
+
order: 100,
|
|
476
|
+
control: 'enum',
|
|
477
|
+
render: ({ doc, target, property }) => createInspectorEnumControl(doc, target, property),
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
id: 'builtin.vec2',
|
|
481
|
+
order: 100,
|
|
482
|
+
control: 'vec2',
|
|
483
|
+
render: ({ doc, target, property }) => createInspectorVectorControl(doc, target, property),
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
id: 'builtin.vec3',
|
|
487
|
+
order: 100,
|
|
488
|
+
control: 'vec3',
|
|
489
|
+
render: ({ doc, target, property }) => createInspectorVectorControl(doc, target, property),
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
id: 'builtin.color',
|
|
493
|
+
order: 100,
|
|
494
|
+
control: 'color',
|
|
495
|
+
render: ({ doc, target, property }) => createInspectorColorControl(doc, target, property),
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
id: 'builtin.asset',
|
|
499
|
+
order: 100,
|
|
500
|
+
control: 'asset',
|
|
501
|
+
render: ({ doc, target, property }) => createInspectorTextControl(doc, target, property),
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
id: 'builtin.object',
|
|
505
|
+
order: 100,
|
|
506
|
+
control: 'object',
|
|
507
|
+
render: ({ doc, target, property }) => createInspectorReadonlyControl(doc, target, property),
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
id: 'builtin.custom',
|
|
511
|
+
order: 1000,
|
|
512
|
+
control: 'custom',
|
|
513
|
+
render: ({ doc, target, property }) => createInspectorReadonlyControl(doc, target, property),
|
|
514
|
+
},
|
|
515
|
+
];
|
|
516
|
+
function appendInspectorPropertyRow(doc, parent, inspectorObject, property, controlRegistry) {
|
|
517
|
+
const control = renderInspectorControl(doc, controlRegistry, inspectorObject, property);
|
|
518
|
+
parent.appendChild(createPropertyRow(doc, property.label, control));
|
|
519
|
+
}
|
|
520
|
+
export function createLocalEditorBrowserInspectorControlRegistry(controls = [], conflict = 'error') {
|
|
521
|
+
const registrations = new Map();
|
|
522
|
+
for (const control of builtinInspectorControlRegistrations) {
|
|
523
|
+
registerInspectorControl(registrations, control, 'error');
|
|
394
524
|
}
|
|
395
|
-
const
|
|
396
|
-
|
|
525
|
+
for (const control of controls) {
|
|
526
|
+
registerInspectorControl(registrations, control, conflict);
|
|
527
|
+
}
|
|
528
|
+
return [...registrations.values()].sort(compareInspectorControlRegistrations);
|
|
397
529
|
}
|
|
398
|
-
function
|
|
530
|
+
function registerInspectorControl(registrations, registration, conflict) {
|
|
531
|
+
const id = registration.id.trim();
|
|
532
|
+
if (!id)
|
|
533
|
+
throw new Error('Inspector control id is required.');
|
|
534
|
+
const normalized = id === registration.id ? registration : { ...registration, id };
|
|
535
|
+
if (registrations.has(id)) {
|
|
536
|
+
if (conflict === 'error')
|
|
537
|
+
throw new Error(`Inspector control "${id}" is already registered.`);
|
|
538
|
+
if (conflict === 'ignore')
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
registrations.set(id, normalized);
|
|
542
|
+
}
|
|
543
|
+
function compareInspectorControlRegistrations(left, right) {
|
|
544
|
+
return (left.order ?? 0) - (right.order ?? 0) || left.id.localeCompare(right.id);
|
|
545
|
+
}
|
|
546
|
+
function renderInspectorControl(doc, registrations, target, property) {
|
|
547
|
+
if (property.readOnly || property.persistence !== 'document')
|
|
548
|
+
return createInspectorReadonlyControl(doc, target, property);
|
|
549
|
+
const context = {
|
|
550
|
+
doc,
|
|
551
|
+
target,
|
|
552
|
+
property,
|
|
553
|
+
bindInput(element, options) {
|
|
554
|
+
applyLocalEditorBrowserInspectorControlBinding(element, target, property, options);
|
|
555
|
+
},
|
|
556
|
+
};
|
|
557
|
+
const registration = resolveLocalEditorBrowserInspectorControlRegistration(registrations, context);
|
|
558
|
+
if (registration)
|
|
559
|
+
return registration.render(context);
|
|
560
|
+
return createInspectorReadonlyControl(doc, target, property);
|
|
561
|
+
}
|
|
562
|
+
export function resolveLocalEditorBrowserInspectorControlRegistration(registrations, context) {
|
|
563
|
+
for (const registration of registrations) {
|
|
564
|
+
if (localEditorBrowserInspectorControlSupports(registration, context))
|
|
565
|
+
return registration;
|
|
566
|
+
}
|
|
567
|
+
return null;
|
|
568
|
+
}
|
|
569
|
+
function localEditorBrowserInspectorControlSupports(registration, context) {
|
|
570
|
+
if (registration.control && registration.control !== context.property.control)
|
|
571
|
+
return false;
|
|
572
|
+
if (registration.customControl && registration.customControl !== context.property.customControl)
|
|
573
|
+
return false;
|
|
574
|
+
return registration.supports?.(context) ?? true;
|
|
575
|
+
}
|
|
576
|
+
function appendInspectorVectorInputs(doc, parent, inspectorObject, label, properties) {
|
|
399
577
|
const wrapper = doc.createElement('div');
|
|
400
578
|
wrapper.style.cssText = 'margin:8px 0';
|
|
401
579
|
const labelElement = doc.createElement('div');
|
|
@@ -405,25 +583,156 @@ function appendSerializedVec3Inputs(doc, parent, serializedObject, label, proper
|
|
|
405
583
|
const fields = doc.createElement('div');
|
|
406
584
|
fields.style.cssText = 'display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px';
|
|
407
585
|
for (const property of properties) {
|
|
408
|
-
fields.appendChild(
|
|
586
|
+
fields.appendChild(createInspectorNumberControl(doc, inspectorObject, property));
|
|
409
587
|
}
|
|
410
588
|
wrapper.appendChild(fields);
|
|
411
589
|
parent.appendChild(wrapper);
|
|
412
590
|
}
|
|
413
|
-
function
|
|
591
|
+
function createInspectorInputBase(doc, target, property) {
|
|
414
592
|
const input = doc.createElement('input');
|
|
415
|
-
input.type = property.valueType === 'number' ? 'number' : 'text';
|
|
416
|
-
if (property.valueType === 'number')
|
|
417
|
-
input.step = '0.1';
|
|
418
593
|
input.value = property.mixed ? '' : String(property.value);
|
|
419
594
|
input.placeholder = property.mixed ? '--' : '';
|
|
420
|
-
input
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
input
|
|
426
|
-
input.
|
|
595
|
+
applyLocalEditorBrowserInspectorControlBinding(input, target, property);
|
|
596
|
+
input.style.cssText = createInspectorInputStyle();
|
|
597
|
+
return input;
|
|
598
|
+
}
|
|
599
|
+
function createInspectorNumberControl(doc, target, property) {
|
|
600
|
+
const input = createInspectorInputBase(doc, target, property);
|
|
601
|
+
input.type = 'number';
|
|
602
|
+
input.step = String(property.step ?? 0.1);
|
|
603
|
+
if (property.min != null)
|
|
604
|
+
input.min = String(property.min);
|
|
605
|
+
if (property.max != null)
|
|
606
|
+
input.max = String(property.max);
|
|
607
|
+
return input;
|
|
608
|
+
}
|
|
609
|
+
function createInspectorTextControl(doc, target, property) {
|
|
610
|
+
const input = createInspectorInputBase(doc, target, property);
|
|
611
|
+
input.type = 'text';
|
|
612
|
+
return input;
|
|
613
|
+
}
|
|
614
|
+
function createInspectorBooleanControl(doc, target, property) {
|
|
615
|
+
const input = createInspectorInputBase(doc, target, property);
|
|
616
|
+
input.type = 'checkbox';
|
|
617
|
+
input.checked = property.mixed ? false : property.value === true;
|
|
618
|
+
input.style.cssText = 'width:16px;height:16px;accent-color:var(--fps-editor-accent);justify-self:start';
|
|
619
|
+
return input;
|
|
620
|
+
}
|
|
621
|
+
function createInspectorEnumControl(doc, target, property) {
|
|
622
|
+
const select = doc.createElement('select');
|
|
623
|
+
applyLocalEditorBrowserInspectorControlBinding(select, target, property);
|
|
624
|
+
select.style.cssText = createInspectorInputStyle();
|
|
625
|
+
for (const option of property.options ?? []) {
|
|
626
|
+
const item = doc.createElement('option');
|
|
627
|
+
item.value = String(option.value);
|
|
628
|
+
item.dataset.serializedOptionValue = JSON.stringify(option.value);
|
|
629
|
+
item.textContent = option.label;
|
|
630
|
+
item.disabled = option.disabled === true;
|
|
631
|
+
if (!property.mixed && option.value === property.value)
|
|
632
|
+
item.selected = true;
|
|
633
|
+
select.appendChild(item);
|
|
634
|
+
}
|
|
635
|
+
if (property.mixed) {
|
|
636
|
+
const mixed = doc.createElement('option');
|
|
637
|
+
mixed.value = '';
|
|
638
|
+
mixed.textContent = '--';
|
|
639
|
+
mixed.selected = true;
|
|
640
|
+
select.prepend(mixed);
|
|
641
|
+
}
|
|
642
|
+
return select;
|
|
643
|
+
}
|
|
644
|
+
function createInspectorVectorControl(doc, target, property) {
|
|
645
|
+
const wrapper = doc.createElement('div');
|
|
646
|
+
wrapper.dataset.inspectorVectorControl = 'true';
|
|
647
|
+
wrapper.style.cssText = 'display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px;width:100%';
|
|
648
|
+
const axes = property.control === 'vec2' ? ['x', 'y'] : ['x', 'y', 'z'];
|
|
649
|
+
if (property.control === 'vec2')
|
|
650
|
+
wrapper.style.gridTemplateColumns = 'repeat(2,minmax(0,1fr))';
|
|
651
|
+
const value = isRecord(property.value) ? property.value : {};
|
|
652
|
+
for (const axis of axes) {
|
|
653
|
+
const input = createInspectorInputBase(doc, target, property);
|
|
654
|
+
input.type = 'number';
|
|
655
|
+
input.step = String(property.step ?? 0.1);
|
|
656
|
+
input.dataset.serializedVectorAxis = axis;
|
|
657
|
+
input.value = property.mixed ? '' : String(value[axis] ?? 0);
|
|
658
|
+
input.title = `${property.label}.${axis}`;
|
|
659
|
+
wrapper.appendChild(input);
|
|
660
|
+
}
|
|
661
|
+
return wrapper;
|
|
662
|
+
}
|
|
663
|
+
function createInspectorColorControl(doc, target, property) {
|
|
664
|
+
const input = createInspectorInputBase(doc, target, property);
|
|
665
|
+
input.type = 'color';
|
|
666
|
+
input.value = colorValueToHex(property.value);
|
|
667
|
+
input.style.cssText = 'width:34px;height:26px;border:1px solid var(--fps-editor-border);border-radius:3px;background:transparent;padding:0';
|
|
668
|
+
return input;
|
|
669
|
+
}
|
|
670
|
+
function createInspectorReadonlyControl(doc, _target, property) {
|
|
671
|
+
if (!property.mixed && isExpandableInspectorValue(property.value)) {
|
|
672
|
+
return createInspectorObjectReadonlyControl(doc, property);
|
|
673
|
+
}
|
|
674
|
+
const valueElement = doc.createElement('div');
|
|
675
|
+
valueElement.textContent = property.mixed ? '--' : formatLocalEditorBrowserInspectorValue(property.value);
|
|
676
|
+
valueElement.style.cssText = [
|
|
677
|
+
`color:${property.persistence === 'runtime' ? 'var(--fps-editor-muted)' : 'var(--fps-editor-text)'}`,
|
|
678
|
+
'overflow:hidden',
|
|
679
|
+
'text-overflow:ellipsis',
|
|
680
|
+
'white-space:nowrap',
|
|
681
|
+
property.persistence === 'runtime' ? 'font-style:italic' : '',
|
|
682
|
+
].filter(Boolean).join(';');
|
|
683
|
+
if (property.persistence === 'runtime')
|
|
684
|
+
valueElement.title = 'Runtime-only context';
|
|
685
|
+
return valueElement;
|
|
686
|
+
}
|
|
687
|
+
function createInspectorObjectReadonlyControl(doc, property) {
|
|
688
|
+
const details = doc.createElement('details');
|
|
689
|
+
details.style.cssText = [
|
|
690
|
+
'min-width:0',
|
|
691
|
+
'max-width:100%',
|
|
692
|
+
`color:${property.persistence === 'runtime' ? 'var(--fps-editor-muted)' : 'var(--fps-editor-text)'}`,
|
|
693
|
+
].join(';');
|
|
694
|
+
const summary = doc.createElement('summary');
|
|
695
|
+
summary.textContent = formatInspectorObjectSummary(property.value);
|
|
696
|
+
summary.style.cssText = 'cursor:pointer;overflow:hidden;text-overflow:ellipsis;white-space:nowrap';
|
|
697
|
+
details.appendChild(summary);
|
|
698
|
+
const pre = doc.createElement('pre');
|
|
699
|
+
pre.textContent = formatLocalEditorBrowserInspectorValue(property.value);
|
|
700
|
+
pre.style.cssText = [
|
|
701
|
+
'max-height:220px',
|
|
702
|
+
'overflow:auto',
|
|
703
|
+
'white-space:pre-wrap',
|
|
704
|
+
'word-break:break-word',
|
|
705
|
+
'margin:6px 0 0',
|
|
706
|
+
'padding:7px',
|
|
707
|
+
'border:1px solid var(--fps-editor-border)',
|
|
708
|
+
'border-radius:3px',
|
|
709
|
+
'background:var(--fps-editor-field)',
|
|
710
|
+
'font-size:11px',
|
|
711
|
+
'line-height:1.45',
|
|
712
|
+
].join(';');
|
|
713
|
+
details.appendChild(pre);
|
|
714
|
+
if (property.persistence === 'runtime')
|
|
715
|
+
details.title = 'Runtime-only context';
|
|
716
|
+
return details;
|
|
717
|
+
}
|
|
718
|
+
export function applyLocalEditorBrowserInspectorControlBinding(element, target, property, options) {
|
|
719
|
+
element.dataset.serializedTargetId = target.activeId ?? target.targetIds[0] ?? '';
|
|
720
|
+
if (target.targetIds.length > 1)
|
|
721
|
+
element.dataset.serializedTargetIds = target.targetIds.join(',');
|
|
722
|
+
element.dataset.serializedPath = property.path;
|
|
723
|
+
element.dataset.serializedControl = property.control;
|
|
724
|
+
element.dataset.serializedValueType = property.valueType;
|
|
725
|
+
element.dataset.serializedCommitMode = property.commitMode;
|
|
726
|
+
element.dataset.serializedPersistence = property.persistence;
|
|
727
|
+
if (options?.source)
|
|
728
|
+
element.dataset.serializedEditSource = options.source;
|
|
729
|
+
element.title = property.tooltip ?? property.label;
|
|
730
|
+
if ('disabled' in element) {
|
|
731
|
+
element.disabled = property.readOnly === true || property.persistence !== 'document';
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
function createInspectorInputStyle() {
|
|
735
|
+
return [
|
|
427
736
|
'min-width:0',
|
|
428
737
|
'height:28px',
|
|
429
738
|
'border:1px solid var(--fps-editor-border)',
|
|
@@ -433,7 +742,6 @@ function createPropertyInput(doc, target, property) {
|
|
|
433
742
|
'font-size:12px',
|
|
434
743
|
'padding:0 6px',
|
|
435
744
|
].join(';');
|
|
436
|
-
return input;
|
|
437
745
|
}
|
|
438
746
|
function appendReadOnlyRow(doc, parent, label, value) {
|
|
439
747
|
const valueElement = doc.createElement('div');
|
|
@@ -441,4 +749,157 @@ function appendReadOnlyRow(doc, parent, label, value) {
|
|
|
441
749
|
valueElement.style.cssText = 'color:var(--fps-editor-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap';
|
|
442
750
|
parent.appendChild(createPropertyRow(doc, label, valueElement));
|
|
443
751
|
}
|
|
752
|
+
function createLegacyInspectorObject(serializedObject) {
|
|
753
|
+
const targetIds = 'targetIds' in serializedObject ? serializedObject.targetIds : [serializedObject.targetId];
|
|
754
|
+
const activeId = 'targetId' in serializedObject ? serializedObject.targetId : serializedObject.activeId;
|
|
755
|
+
return {
|
|
756
|
+
targetIds,
|
|
757
|
+
activeId,
|
|
758
|
+
label: serializedObject.label,
|
|
759
|
+
document: serializedObject.document,
|
|
760
|
+
selection: {
|
|
761
|
+
targetIds,
|
|
762
|
+
activeId,
|
|
763
|
+
document: serializedObject.document,
|
|
764
|
+
},
|
|
765
|
+
sections: createLegacyInspectorSections(serializedObject.properties),
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
function createLegacyInspectorSections(properties) {
|
|
769
|
+
const sections = new Map();
|
|
770
|
+
for (const [index, property] of properties.entries()) {
|
|
771
|
+
const id = property.path.split('.')[0] || 'properties';
|
|
772
|
+
const section = sections.get(id) ?? { order: sections.size, properties: [] };
|
|
773
|
+
section.properties.push({
|
|
774
|
+
path: property.path,
|
|
775
|
+
label: property.label,
|
|
776
|
+
valueType: property.valueType,
|
|
777
|
+
control: inferLegacyInspectorControl(property),
|
|
778
|
+
value: property.value,
|
|
779
|
+
mixed: property.mixed,
|
|
780
|
+
readOnly: property.readOnly === true,
|
|
781
|
+
persistence: property.readOnly === true ? 'readonly' : 'document',
|
|
782
|
+
commitMode: inferLegacyInspectorCommitMode(property),
|
|
783
|
+
order: index,
|
|
784
|
+
document: property.document,
|
|
785
|
+
});
|
|
786
|
+
sections.set(id, section);
|
|
787
|
+
}
|
|
788
|
+
return [...sections.entries()].map(([id, section]) => ({
|
|
789
|
+
id,
|
|
790
|
+
title: toTitle(id),
|
|
791
|
+
order: section.order,
|
|
792
|
+
placement: id === 'gameObject' ? 'summary' : 'body',
|
|
793
|
+
persistence: section.properties.some(property => property.persistence === 'document') ? 'document' : 'readonly',
|
|
794
|
+
properties: section.properties,
|
|
795
|
+
}));
|
|
796
|
+
}
|
|
797
|
+
function createSelectionSummaryInspectorObject(state) {
|
|
798
|
+
const targetIds = state.selectedIds;
|
|
799
|
+
if (targetIds.length === 0)
|
|
800
|
+
return null;
|
|
801
|
+
const activeId = state.selectionSummary?.activeId ?? state.activeId ?? targetIds[0] ?? null;
|
|
802
|
+
return {
|
|
803
|
+
targetIds,
|
|
804
|
+
activeId,
|
|
805
|
+
label: `${targetIds.length} objects`,
|
|
806
|
+
selection: {
|
|
807
|
+
targetIds,
|
|
808
|
+
activeId,
|
|
809
|
+
},
|
|
810
|
+
sections: [],
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
function inferLegacyInspectorControl(property) {
|
|
814
|
+
if (property.readOnly)
|
|
815
|
+
return 'readonly';
|
|
816
|
+
if (property.valueType === 'string')
|
|
817
|
+
return 'string';
|
|
818
|
+
if (property.valueType === 'number')
|
|
819
|
+
return 'number';
|
|
820
|
+
if (property.valueType === 'boolean')
|
|
821
|
+
return 'boolean';
|
|
822
|
+
if (property.valueType === 'enum')
|
|
823
|
+
return 'enum';
|
|
824
|
+
if (property.valueType === 'asset')
|
|
825
|
+
return 'asset';
|
|
826
|
+
if (property.valueType === 'object')
|
|
827
|
+
return 'object';
|
|
828
|
+
return 'readonly';
|
|
829
|
+
}
|
|
830
|
+
function inferLegacyInspectorCommitMode(property) {
|
|
831
|
+
switch (property.valueType) {
|
|
832
|
+
case 'number':
|
|
833
|
+
return 'live';
|
|
834
|
+
case 'boolean':
|
|
835
|
+
case 'enum':
|
|
836
|
+
case 'asset':
|
|
837
|
+
return 'immediate';
|
|
838
|
+
default:
|
|
839
|
+
return 'blur';
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
export function formatLocalEditorBrowserInspectorValue(value) {
|
|
843
|
+
if (value == null)
|
|
844
|
+
return '';
|
|
845
|
+
if (typeof value === 'object')
|
|
846
|
+
return JSON.stringify(toStableInspectorJsonValue(value), null, 2);
|
|
847
|
+
if (typeof value === 'function')
|
|
848
|
+
return `[Function ${value.name || 'anonymous'}]`;
|
|
849
|
+
if (typeof value === 'symbol')
|
|
850
|
+
return String(value);
|
|
851
|
+
return String(value);
|
|
852
|
+
}
|
|
853
|
+
function isRecord(value) {
|
|
854
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
855
|
+
}
|
|
856
|
+
function isExpandableInspectorValue(value) {
|
|
857
|
+
return !!value && typeof value === 'object';
|
|
858
|
+
}
|
|
859
|
+
function formatInspectorObjectSummary(value) {
|
|
860
|
+
if (Array.isArray(value))
|
|
861
|
+
return `Array(${value.length})`;
|
|
862
|
+
if (!isRecord(value))
|
|
863
|
+
return 'Object';
|
|
864
|
+
const keys = Object.keys(value).sort();
|
|
865
|
+
if (keys.length === 0)
|
|
866
|
+
return 'Object {}';
|
|
867
|
+
const shown = keys.slice(0, 4).join(', ');
|
|
868
|
+
return keys.length > 4 ? `Object { ${shown}, ... }` : `Object { ${shown} }`;
|
|
869
|
+
}
|
|
870
|
+
function toStableInspectorJsonValue(value, seen = new WeakSet(), depth = 0) {
|
|
871
|
+
if (value == null || typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean')
|
|
872
|
+
return value;
|
|
873
|
+
if (typeof value === 'bigint')
|
|
874
|
+
return String(value);
|
|
875
|
+
if (typeof value === 'function')
|
|
876
|
+
return `[Function ${value.name || 'anonymous'}]`;
|
|
877
|
+
if (typeof value === 'symbol')
|
|
878
|
+
return String(value);
|
|
879
|
+
if (typeof value !== 'object')
|
|
880
|
+
return String(value);
|
|
881
|
+
if (seen.has(value))
|
|
882
|
+
return '[Circular]';
|
|
883
|
+
if (depth > 6)
|
|
884
|
+
return '[MaxDepth]';
|
|
885
|
+
seen.add(value);
|
|
886
|
+
if (Array.isArray(value))
|
|
887
|
+
return value.map(item => toStableInspectorJsonValue(item, seen, depth + 1));
|
|
888
|
+
const record = value;
|
|
889
|
+
const output = {};
|
|
890
|
+
for (const key of Object.keys(record).sort()) {
|
|
891
|
+
output[key] = toStableInspectorJsonValue(record[key], seen, depth + 1);
|
|
892
|
+
}
|
|
893
|
+
return output;
|
|
894
|
+
}
|
|
895
|
+
function colorValueToHex(value) {
|
|
896
|
+
if (!value || typeof value !== 'object')
|
|
897
|
+
return '#ffffff';
|
|
898
|
+
const color = value;
|
|
899
|
+
const toHex = (channel) => {
|
|
900
|
+
const value = typeof channel === 'number' && Number.isFinite(channel) ? channel : 1;
|
|
901
|
+
return Math.round(Math.max(0, Math.min(1, value)) * 255).toString(16).padStart(2, '0');
|
|
902
|
+
};
|
|
903
|
+
return `#${toHex(color.r)}${toHex(color.g)}${toHex(color.b)}`;
|
|
904
|
+
}
|
|
444
905
|
//# sourceMappingURL=local-editor-ui-panels.js.map
|