@fps-games/editor 0.1.1-beta.6 → 0.1.1-beta.7
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 +14 -3
- package/dist/local-editor-harness.d.ts.map +1 -1
- package/dist/local-editor-harness.js +205 -35
- package/dist/local-editor-harness.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.d.ts +33 -0
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.js +231 -0
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.js.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/editor-world.d.ts +3 -0
- package/node_modules/@fps-games/editor-babylon/dist/editor-world.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/editor-world.js +8 -0
- package/node_modules/@fps-games/editor-babylon/dist/editor-world.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/index.d.ts +2 -0
- package/node_modules/@fps-games/editor-babylon/dist/index.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/index.js +2 -0
- package/node_modules/@fps-games/editor-babylon/dist/index.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.d.ts +40 -0
- package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.js +237 -0
- package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.js.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/projection-selection-controller.d.ts +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection-selection-controller.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection-selection-controller.js +15 -50
- package/node_modules/@fps-games/editor-babylon/dist/projection-selection-controller.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts +5 -0
- package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection.js +61 -8
- package/node_modules/@fps-games/editor-babylon/dist/projection.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/types.d.ts +7 -0
- package/node_modules/@fps-games/editor-babylon/dist/types.d.ts.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.js +97 -46
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-context-menu.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.d.ts +5 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.js +57 -7
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.js +61 -5
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-tree.js +0 -2
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-tree.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-view.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-view.js +6 -2
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-view.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.d.ts +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.js +16 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.d.ts +8 -1
- 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 +358 -53
- 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-shared.d.ts +5 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shared.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shared.js +28 -8
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shared.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 +4 -1
- 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-theme.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-theme.js +14 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-theme.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-tooltip.d.ts +11 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-tooltip.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-tooltip.js +148 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-tooltip.js.map +1 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts +42 -1
- 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 +5 -4
- 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 +606 -199
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/package.json +2 -2
- package/node_modules/@fps-games/editor-core/dist/index.d.ts +1 -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 +1 -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 +6 -1
- package/node_modules/@fps-games/editor-core/dist/inspector.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-core/dist/inspector.js +6 -1
- package/node_modules/@fps-games/editor-core/dist/inspector.js.map +1 -1
- package/node_modules/@fps-games/editor-core/dist/scene-graph.d.ts +7 -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.map +1 -1
- package/node_modules/@fps-games/editor-core/dist/selection-strategy.d.ts +12 -0
- package/node_modules/@fps-games/editor-core/dist/selection-strategy.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-core/dist/selection-strategy.js +52 -0
- package/node_modules/@fps-games/editor-core/dist/selection-strategy.js.map +1 -0
- package/node_modules/@fps-games/editor-core/package.json +2 -2
- 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 +6 -6
|
@@ -7,11 +7,11 @@ const DOCK_TAB_ICONS = {
|
|
|
7
7
|
};
|
|
8
8
|
export function renderHierarchyPanel(doc, panel, state, rename, drop) {
|
|
9
9
|
clearElement(panel);
|
|
10
|
-
const createGroupButton = createToolbarButton(doc, '+
|
|
10
|
+
const createGroupButton = createToolbarButton(doc, '+ Empty', 'object');
|
|
11
11
|
createGroupButton.dataset.editorHierarchyCreateGroup = 'true';
|
|
12
12
|
createGroupButton.style.padding = '3px 7px';
|
|
13
13
|
createGroupButton.style.fontSize = '11px';
|
|
14
|
-
panel.appendChild(createPanelHeader(doc, '
|
|
14
|
+
panel.appendChild(createPanelHeader(doc, 'Hierarchy', [createGroupButton], 'hierarchy'));
|
|
15
15
|
const list = createTreeView(doc);
|
|
16
16
|
for (const item of state.hierarchy) {
|
|
17
17
|
const isRenaming = rename?.id === item.id;
|
|
@@ -256,6 +256,7 @@ function formatHistoryTimestamp(createdAt) {
|
|
|
256
256
|
].join('');
|
|
257
257
|
}
|
|
258
258
|
export function renderInspectorPanel(doc, panel, state, filter = '', options = {}) {
|
|
259
|
+
const renderSnapshot = captureInspectorPanelRenderSnapshot(doc, panel);
|
|
259
260
|
clearElement(panel);
|
|
260
261
|
const title = doc.createElement('h2');
|
|
261
262
|
title.style.cssText = 'display:flex;align-items:center;gap:6px;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)';
|
|
@@ -274,23 +275,45 @@ export function renderInspectorPanel(doc, panel, state, filter = '', options = {
|
|
|
274
275
|
empty.textContent = '请从层级树或 Scene View 中选择一个 GameObject。';
|
|
275
276
|
empty.style.cssText = 'color:var(--fps-editor-muted);line-height:1.45';
|
|
276
277
|
panel.appendChild(empty);
|
|
278
|
+
restoreInspectorPanelRenderSnapshot(panel, renderSnapshot);
|
|
277
279
|
return;
|
|
278
280
|
}
|
|
279
281
|
const controlRegistry = createLocalEditorBrowserInspectorControlRegistry(options.controls, options.controlConflict);
|
|
280
282
|
appendInspectorSummary(doc, panel, inspectorObject, selectionCount);
|
|
281
283
|
const visibleSections = filterInspectorSections(inspectorObject.sections.filter(section => section.placement !== 'summary'), filter);
|
|
282
284
|
if (visibleSections.length === 0) {
|
|
283
|
-
if (!filter.trim() && inspectorObject.sections.length === 0)
|
|
285
|
+
if (!filter.trim() && inspectorObject.sections.length === 0) {
|
|
286
|
+
restoreInspectorPanelRenderSnapshot(panel, renderSnapshot);
|
|
284
287
|
return;
|
|
288
|
+
}
|
|
285
289
|
const empty = doc.createElement('div');
|
|
286
290
|
empty.textContent = '没有匹配的 Inspector 字段。';
|
|
287
291
|
empty.style.cssText = 'color:var(--fps-editor-muted);line-height:1.45';
|
|
288
292
|
panel.appendChild(empty);
|
|
293
|
+
restoreInspectorPanelRenderSnapshot(panel, renderSnapshot);
|
|
289
294
|
return;
|
|
290
295
|
}
|
|
291
296
|
for (const section of visibleSections) {
|
|
292
297
|
panel.appendChild(createInspectorSectionBlock(doc, inspectorObject, section, controlRegistry));
|
|
293
298
|
}
|
|
299
|
+
restoreInspectorPanelRenderSnapshot(panel, renderSnapshot);
|
|
300
|
+
}
|
|
301
|
+
function captureInspectorPanelRenderSnapshot(doc, panel) {
|
|
302
|
+
const activeElement = doc.activeElement;
|
|
303
|
+
const elementConstructor = doc.defaultView?.HTMLElement;
|
|
304
|
+
return {
|
|
305
|
+
preserveScroll: !!activeElement
|
|
306
|
+
&& (elementConstructor ? activeElement instanceof elementConstructor : activeElement instanceof HTMLElement)
|
|
307
|
+
&& panel.contains(activeElement),
|
|
308
|
+
scrollTop: panel.scrollTop,
|
|
309
|
+
scrollLeft: panel.scrollLeft,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
function restoreInspectorPanelRenderSnapshot(panel, snapshot) {
|
|
313
|
+
if (!snapshot.preserveScroll)
|
|
314
|
+
return;
|
|
315
|
+
panel.scrollTop = snapshot.scrollTop;
|
|
316
|
+
panel.scrollLeft = snapshot.scrollLeft;
|
|
294
317
|
}
|
|
295
318
|
function createInspectorSearchInput(doc, value) {
|
|
296
319
|
const input = doc.createElement('input');
|
|
@@ -308,10 +331,14 @@ function createInspectorSearchInput(doc, value) {
|
|
|
308
331
|
}
|
|
309
332
|
function appendInspectorSummary(doc, panel, inspectorObject, selectionCount) {
|
|
310
333
|
const block = createInspectorComponentBlock(doc);
|
|
334
|
+
const header = doc.createElement('div');
|
|
335
|
+
header.style.cssText = 'display:flex;align-items:center;justify-content:space-between;gap:8px;margin:0 0 8px';
|
|
311
336
|
const title = doc.createElement('h3');
|
|
312
337
|
title.textContent = selectionCount > 1 ? 'Selection' : 'GameObject';
|
|
313
|
-
title.style.cssText = 'font-size:12px;margin:0
|
|
314
|
-
|
|
338
|
+
title.style.cssText = 'font-size:12px;margin:0;font-weight:900;color:var(--fps-editor-text-strong)';
|
|
339
|
+
header.appendChild(title);
|
|
340
|
+
header.appendChild(createInspectorAccessBadge(doc, 'readonly'));
|
|
341
|
+
block.appendChild(header);
|
|
315
342
|
appendReadOnlyRow(doc, block, selectionCount > 1 ? 'Selected' : 'Name', inspectorObject.label ?? inspectorObject.activeId ?? inspectorObject.targetIds[0] ?? '无');
|
|
316
343
|
appendReadOnlyRow(doc, block, 'Active ID', inspectorObject.activeId ?? '无');
|
|
317
344
|
if (selectionCount > 1)
|
|
@@ -342,7 +369,11 @@ function createInspectorSectionBlock(doc, inspectorObject, section, controlRegis
|
|
|
342
369
|
const block = doc.createElement('details');
|
|
343
370
|
block.dataset.editorInspectorSection = section.id;
|
|
344
371
|
block.open = section.collapsedByDefault !== true;
|
|
345
|
-
|
|
372
|
+
const status = resolveLocalEditorBrowserInspectorSectionStatus(section);
|
|
373
|
+
const { access, effect } = status;
|
|
374
|
+
block.dataset.editorInspectorAccess = access;
|
|
375
|
+
block.dataset.editorInspectorEffect = effect;
|
|
376
|
+
block.style.cssText = createInspectorBlockStyle(access, effect);
|
|
346
377
|
const sectionTitle = doc.createElement('summary');
|
|
347
378
|
sectionTitle.style.cssText = [
|
|
348
379
|
'font-size:12px',
|
|
@@ -367,13 +398,13 @@ function createInspectorSectionBlock(doc, inspectorObject, section, controlRegis
|
|
|
367
398
|
summary.style.cssText = 'overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-transform:none;font-size:11px';
|
|
368
399
|
meta.appendChild(summary);
|
|
369
400
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
meta.appendChild(
|
|
401
|
+
const badge = createInspectorAccessBadge(doc, access);
|
|
402
|
+
badge.style.flex = '0 0 auto';
|
|
403
|
+
meta.appendChild(badge);
|
|
404
|
+
const effectBadge = createInspectorEffectBadge(doc, effect, section.disabledReason);
|
|
405
|
+
if (effectBadge && status.showEffectBadge) {
|
|
406
|
+
effectBadge.style.flex = '0 0 auto';
|
|
407
|
+
meta.appendChild(effectBadge);
|
|
377
408
|
}
|
|
378
409
|
sectionTitle.appendChild(meta);
|
|
379
410
|
block.appendChild(sectionTitle);
|
|
@@ -390,18 +421,133 @@ function createInspectorSectionBlock(doc, inspectorObject, section, controlRegis
|
|
|
390
421
|
}
|
|
391
422
|
function createInspectorComponentBlock(doc) {
|
|
392
423
|
const block = doc.createElement('div');
|
|
393
|
-
block.
|
|
424
|
+
block.dataset.editorInspectorAccess = 'readonly';
|
|
425
|
+
block.dataset.editorInspectorEffect = 'active';
|
|
426
|
+
block.style.cssText = createInspectorBlockStyle('readonly', 'active');
|
|
394
427
|
return block;
|
|
395
428
|
}
|
|
396
|
-
function
|
|
429
|
+
export function resolveLocalEditorBrowserInspectorSectionStatus(section) {
|
|
430
|
+
const access = getInspectorSectionAccess(section);
|
|
431
|
+
const effect = getInspectorSectionEffect(section);
|
|
432
|
+
return {
|
|
433
|
+
access,
|
|
434
|
+
effect,
|
|
435
|
+
showEffectBadge: effect !== 'active'
|
|
436
|
+
&& !(access === 'runtime' && effect === 'runtime')
|
|
437
|
+
&& !(effect === 'default' && inspectorSummaryLooksDefault(section.summary)),
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
function createInspectorBlockStyle(_access, _effect = 'active') {
|
|
397
441
|
return [
|
|
398
442
|
'margin:0 0 8px',
|
|
399
443
|
'padding:9px',
|
|
400
|
-
'border:1px solid var(--fps-editor-border)',
|
|
444
|
+
'border:1px solid var(--fps-editor-inspector-section-border, var(--fps-editor-border))',
|
|
401
445
|
'border-radius:3px',
|
|
402
446
|
'background:var(--fps-editor-panel-soft)',
|
|
403
447
|
].join(';');
|
|
404
448
|
}
|
|
449
|
+
function isInspectorPropertyEditable(property) {
|
|
450
|
+
return property.readOnly !== true
|
|
451
|
+
&& property.persistence === 'document'
|
|
452
|
+
&& getInspectorPropertyEffect(property) === 'active';
|
|
453
|
+
}
|
|
454
|
+
function getInspectorPropertyAccess(property) {
|
|
455
|
+
if (property.persistence === 'runtime')
|
|
456
|
+
return 'runtime';
|
|
457
|
+
if (getInspectorPropertyEffect(property) === 'runtime')
|
|
458
|
+
return 'runtime';
|
|
459
|
+
return isInspectorPropertyEditable(property) ? 'editable' : 'readonly';
|
|
460
|
+
}
|
|
461
|
+
function getInspectorSectionAccess(section) {
|
|
462
|
+
if (section.runtimeOnly || section.persistence === 'runtime' || getInspectorSectionEffect(section) === 'runtime')
|
|
463
|
+
return 'runtime';
|
|
464
|
+
const editableCount = section.properties.filter(isInspectorPropertyEditable).length;
|
|
465
|
+
if (editableCount === 0)
|
|
466
|
+
return 'readonly';
|
|
467
|
+
if (editableCount === section.properties.length)
|
|
468
|
+
return 'editable';
|
|
469
|
+
return 'mixed';
|
|
470
|
+
}
|
|
471
|
+
function getInspectorPropertyEffect(property) {
|
|
472
|
+
if (property.effect)
|
|
473
|
+
return property.effect;
|
|
474
|
+
if (property.persistence === 'runtime')
|
|
475
|
+
return 'runtime';
|
|
476
|
+
return 'active';
|
|
477
|
+
}
|
|
478
|
+
function getInspectorSectionEffect(section) {
|
|
479
|
+
if (section.effect)
|
|
480
|
+
return section.effect;
|
|
481
|
+
if (section.runtimeOnly || section.persistence === 'runtime')
|
|
482
|
+
return 'runtime';
|
|
483
|
+
if (section.properties.some(isInspectorPropertyEditable))
|
|
484
|
+
return 'active';
|
|
485
|
+
const effects = new Set(section.properties.map(getInspectorPropertyEffect));
|
|
486
|
+
if (effects.size === 1)
|
|
487
|
+
return [...effects][0] ?? 'active';
|
|
488
|
+
if (effects.has('unsupported'))
|
|
489
|
+
return 'unsupported';
|
|
490
|
+
if (effects.has('default'))
|
|
491
|
+
return 'default';
|
|
492
|
+
if (effects.has('derived'))
|
|
493
|
+
return 'derived';
|
|
494
|
+
if (effects.has('runtime'))
|
|
495
|
+
return 'runtime';
|
|
496
|
+
return 'active';
|
|
497
|
+
}
|
|
498
|
+
function createInspectorAccessBadge(doc, access) {
|
|
499
|
+
const labels = {
|
|
500
|
+
editable: 'EDITABLE',
|
|
501
|
+
mixed: 'MIXED',
|
|
502
|
+
readonly: 'READONLY',
|
|
503
|
+
runtime: 'RUNTIME',
|
|
504
|
+
};
|
|
505
|
+
const badge = createBadge(doc, labels[access], {
|
|
506
|
+
compact: true,
|
|
507
|
+
tone: access === 'editable' ? 'success' : access === 'mixed' || access === 'runtime' ? 'warning' : 'default',
|
|
508
|
+
});
|
|
509
|
+
badge.title = access === 'editable'
|
|
510
|
+
? 'This section writes to the document.'
|
|
511
|
+
: access === 'mixed'
|
|
512
|
+
? 'This section contains both editable and read-only fields.'
|
|
513
|
+
: access === 'runtime'
|
|
514
|
+
? 'Runtime-only context, not saved to the document.'
|
|
515
|
+
: 'Read-only information.';
|
|
516
|
+
return badge;
|
|
517
|
+
}
|
|
518
|
+
function createInspectorEffectBadge(doc, effect, disabledReason) {
|
|
519
|
+
if (effect === 'active')
|
|
520
|
+
return null;
|
|
521
|
+
const labels = {
|
|
522
|
+
default: 'DEFAULTS',
|
|
523
|
+
derived: 'DERIVED',
|
|
524
|
+
runtime: 'RUNTIME',
|
|
525
|
+
unsupported: 'NO EFFECT',
|
|
526
|
+
};
|
|
527
|
+
const badge = createBadge(doc, labels[effect], {
|
|
528
|
+
compact: true,
|
|
529
|
+
tone: effect === 'unsupported' ? 'danger' : 'warning',
|
|
530
|
+
});
|
|
531
|
+
badge.title = disabledReason ?? createInspectorEffectTitle(effect);
|
|
532
|
+
return badge;
|
|
533
|
+
}
|
|
534
|
+
function createInspectorEffectTitle(effect) {
|
|
535
|
+
switch (effect) {
|
|
536
|
+
case 'default':
|
|
537
|
+
return 'Default values are being shown; edits do not affect this object until an override is configured.';
|
|
538
|
+
case 'derived':
|
|
539
|
+
return 'Derived from document or runtime state.';
|
|
540
|
+
case 'runtime':
|
|
541
|
+
return 'Runtime-only context, not saved to the document.';
|
|
542
|
+
case 'unsupported':
|
|
543
|
+
return 'This field is visible but editing is not supported for the current object.';
|
|
544
|
+
default:
|
|
545
|
+
return 'Editable field.';
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
function inspectorSummaryLooksDefault(summary) {
|
|
549
|
+
return summary?.trim().toLowerCase() === 'defaults';
|
|
550
|
+
}
|
|
405
551
|
function groupInspectorProperties(properties) {
|
|
406
552
|
const groups = [];
|
|
407
553
|
const consumed = new Set();
|
|
@@ -430,7 +576,7 @@ function groupInspectorProperties(properties) {
|
|
|
430
576
|
for (const entry of vector)
|
|
431
577
|
consumed.add(entry.path);
|
|
432
578
|
const parts = basePath.split('.');
|
|
433
|
-
groups.push({ kind: 'vector', label:
|
|
579
|
+
groups.push({ kind: 'vector', label: resolveInspectorVectorGroupLabel(vector, parts[parts.length - 1] ?? basePath), properties: vector });
|
|
434
580
|
}
|
|
435
581
|
else {
|
|
436
582
|
groups.push({ kind: 'property', property });
|
|
@@ -439,6 +585,16 @@ function groupInspectorProperties(properties) {
|
|
|
439
585
|
}
|
|
440
586
|
return groups;
|
|
441
587
|
}
|
|
588
|
+
function resolveInspectorVectorGroupLabel(properties, fallback) {
|
|
589
|
+
const labels = properties.map(property => {
|
|
590
|
+
const match = property.label.match(/^(.*)\.(x|y|z|r|g|b)$/);
|
|
591
|
+
return match?.[1]?.trim() ?? '';
|
|
592
|
+
});
|
|
593
|
+
const first = labels[0];
|
|
594
|
+
if (first && labels.every(label => label === first))
|
|
595
|
+
return first;
|
|
596
|
+
return toTitle(fallback);
|
|
597
|
+
}
|
|
442
598
|
const builtinInspectorControlRegistrations = [
|
|
443
599
|
{
|
|
444
600
|
id: 'builtin.readonly',
|
|
@@ -509,7 +665,16 @@ const builtinInspectorControlRegistrations = [
|
|
|
509
665
|
];
|
|
510
666
|
function appendInspectorPropertyRow(doc, parent, inspectorObject, property, controlRegistry) {
|
|
511
667
|
const control = renderInspectorControl(doc, controlRegistry, inspectorObject, property);
|
|
512
|
-
|
|
668
|
+
const access = getInspectorPropertyAccess(property);
|
|
669
|
+
const effect = getInspectorPropertyEffect(property);
|
|
670
|
+
const row = createPropertyRow(doc, property.label, control);
|
|
671
|
+
row.dataset.editorInspectorAccess = access;
|
|
672
|
+
row.dataset.editorInspectorEffect = effect;
|
|
673
|
+
row.title = createInspectorStatusTitle(access, effect, property.tooltip ?? property.disabledReason);
|
|
674
|
+
const label = row.firstElementChild;
|
|
675
|
+
if (label)
|
|
676
|
+
label.style.cssText = createInspectorPropertyLabelStyle(access, effect);
|
|
677
|
+
parent.appendChild(row);
|
|
513
678
|
}
|
|
514
679
|
export function createLocalEditorBrowserInspectorControlRegistry(controls = [], conflict = 'error') {
|
|
515
680
|
const registrations = new Map();
|
|
@@ -538,7 +703,7 @@ function compareInspectorControlRegistrations(left, right) {
|
|
|
538
703
|
return (left.order ?? 0) - (right.order ?? 0) || left.id.localeCompare(right.id);
|
|
539
704
|
}
|
|
540
705
|
function renderInspectorControl(doc, registrations, target, property) {
|
|
541
|
-
if (property
|
|
706
|
+
if (!isInspectorPropertyEditable(property))
|
|
542
707
|
return createInspectorReadonlyControl(doc, target, property);
|
|
543
708
|
const context = {
|
|
544
709
|
doc,
|
|
@@ -570,18 +735,145 @@ function localEditorBrowserInspectorControlSupports(registration, context) {
|
|
|
570
735
|
function appendInspectorVectorInputs(doc, parent, inspectorObject, label, properties) {
|
|
571
736
|
const wrapper = doc.createElement('div');
|
|
572
737
|
wrapper.style.cssText = 'margin:8px 0';
|
|
738
|
+
const access = getInspectorPropertyGroupAccess(properties);
|
|
739
|
+
const effect = getInspectorPropertyGroupEffect(properties);
|
|
740
|
+
wrapper.dataset.editorInspectorAccess = access;
|
|
741
|
+
wrapper.dataset.editorInspectorEffect = effect;
|
|
573
742
|
const labelElement = doc.createElement('div');
|
|
574
|
-
labelElement.
|
|
575
|
-
|
|
743
|
+
labelElement.style.cssText = 'display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:5px';
|
|
744
|
+
const labelText = doc.createElement('span');
|
|
745
|
+
labelText.textContent = label;
|
|
746
|
+
labelText.style.cssText = createInspectorPropertyLabelStyle(access, effect);
|
|
747
|
+
labelElement.appendChild(labelText);
|
|
748
|
+
if (access !== 'editable') {
|
|
749
|
+
const badge = createInspectorAccessBadge(doc, access);
|
|
750
|
+
badge.style.flex = '0 0 auto';
|
|
751
|
+
labelElement.appendChild(badge);
|
|
752
|
+
}
|
|
753
|
+
const effectBadge = createInspectorEffectBadge(doc, effect, getInspectorPropertyGroupDisabledReason(properties));
|
|
754
|
+
if (effectBadge && !(access === 'runtime' && effect === 'runtime')) {
|
|
755
|
+
effectBadge.style.flex = '0 0 auto';
|
|
756
|
+
labelElement.appendChild(effectBadge);
|
|
757
|
+
}
|
|
576
758
|
wrapper.appendChild(labelElement);
|
|
577
759
|
const fields = doc.createElement('div');
|
|
578
760
|
fields.style.cssText = 'display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px';
|
|
579
761
|
for (const property of properties) {
|
|
580
|
-
fields.appendChild(
|
|
762
|
+
fields.appendChild(isInspectorPropertyEditable(property)
|
|
763
|
+
? createInspectorNumberControl(doc, inspectorObject, property)
|
|
764
|
+
: createInspectorReadonlyControl(doc, inspectorObject, property));
|
|
581
765
|
}
|
|
582
766
|
wrapper.appendChild(fields);
|
|
583
767
|
parent.appendChild(wrapper);
|
|
584
768
|
}
|
|
769
|
+
function getInspectorPropertyGroupAccess(properties) {
|
|
770
|
+
if (properties.some(property => property.persistence === 'runtime'))
|
|
771
|
+
return 'runtime';
|
|
772
|
+
const editableCount = properties.filter(isInspectorPropertyEditable).length;
|
|
773
|
+
if (editableCount === 0)
|
|
774
|
+
return 'readonly';
|
|
775
|
+
if (editableCount === properties.length)
|
|
776
|
+
return 'editable';
|
|
777
|
+
return 'mixed';
|
|
778
|
+
}
|
|
779
|
+
function getInspectorPropertyGroupEffect(properties) {
|
|
780
|
+
const effects = new Set(properties.map(getInspectorPropertyEffect));
|
|
781
|
+
if (effects.size === 1)
|
|
782
|
+
return [...effects][0] ?? 'active';
|
|
783
|
+
if (effects.has('unsupported'))
|
|
784
|
+
return 'unsupported';
|
|
785
|
+
if (effects.has('default'))
|
|
786
|
+
return 'default';
|
|
787
|
+
if (effects.has('derived'))
|
|
788
|
+
return 'derived';
|
|
789
|
+
if (effects.has('runtime'))
|
|
790
|
+
return 'runtime';
|
|
791
|
+
return 'active';
|
|
792
|
+
}
|
|
793
|
+
function getInspectorPropertyGroupDisabledReason(properties) {
|
|
794
|
+
return properties.find(property => property.disabledReason)?.disabledReason;
|
|
795
|
+
}
|
|
796
|
+
function createInspectorPropertyLabelStyle(access, effect = 'active') {
|
|
797
|
+
return [
|
|
798
|
+
`color:${effect === 'unsupported' ? 'var(--fps-editor-danger-text)' : effect === 'default' || effect === 'derived' || access === 'runtime' ? 'var(--fps-editor-warn)' : access === 'editable' ? 'var(--fps-editor-muted-strong)' : 'var(--fps-editor-muted)'}`,
|
|
799
|
+
'font-weight:800',
|
|
800
|
+
access === 'readonly' ? 'opacity:0.82' : '',
|
|
801
|
+
].filter(Boolean).join(';');
|
|
802
|
+
}
|
|
803
|
+
function createInspectorAccessTitle(access, tooltip) {
|
|
804
|
+
const accessLabel = access === 'editable'
|
|
805
|
+
? 'Editable'
|
|
806
|
+
: access === 'mixed'
|
|
807
|
+
? 'Mixed editable and read-only'
|
|
808
|
+
: access === 'runtime'
|
|
809
|
+
? 'Runtime-only'
|
|
810
|
+
: 'Read-only';
|
|
811
|
+
return tooltip ? `${accessLabel}: ${tooltip}` : accessLabel;
|
|
812
|
+
}
|
|
813
|
+
function createInspectorStatusTitle(access, effect, detail) {
|
|
814
|
+
const parts = [createInspectorAccessTitle(access)];
|
|
815
|
+
if (effect !== 'active')
|
|
816
|
+
parts.push(createInspectorEffectTitle(effect));
|
|
817
|
+
if (detail)
|
|
818
|
+
parts.push(detail);
|
|
819
|
+
return [...new Set(parts)].join(' ');
|
|
820
|
+
}
|
|
821
|
+
function createInspectorReadonlyValueShell(doc, access, effect = 'active', title) {
|
|
822
|
+
const valueElement = doc.createElement('div');
|
|
823
|
+
valueElement.dataset.editorInspectorAccess = access;
|
|
824
|
+
valueElement.dataset.editorInspectorEffect = effect;
|
|
825
|
+
valueElement.title = title ?? createInspectorStatusTitle(access, effect);
|
|
826
|
+
const warningLike = effect === 'default' || effect === 'derived' || access === 'runtime' || effect === 'runtime';
|
|
827
|
+
const unsupported = effect === 'unsupported';
|
|
828
|
+
valueElement.style.cssText = [
|
|
829
|
+
'min-width:0',
|
|
830
|
+
'min-height:24px',
|
|
831
|
+
'display:flex',
|
|
832
|
+
'align-items:center',
|
|
833
|
+
'gap:5px',
|
|
834
|
+
'padding:3px 6px',
|
|
835
|
+
`border:1px solid ${unsupported ? 'var(--fps-editor-danger-border)' : warningLike ? 'var(--fps-editor-warn-border)' : 'var(--fps-editor-readonly-border)'}`,
|
|
836
|
+
'border-radius:3px',
|
|
837
|
+
`background:${unsupported ? 'var(--fps-editor-danger-soft)' : warningLike ? 'var(--fps-editor-warn-soft)' : 'var(--fps-editor-readonly-bg)'}`,
|
|
838
|
+
`color:${unsupported ? 'var(--fps-editor-danger-text)' : warningLike ? 'var(--fps-editor-warn)' : 'var(--fps-editor-readonly-text)'}`,
|
|
839
|
+
warningLike ? 'font-style:italic' : '',
|
|
840
|
+
].filter(Boolean).join(';');
|
|
841
|
+
const icon = createLocalEditorIcon(doc, 'lock', { size: 11, strokeWidth: 2.3 });
|
|
842
|
+
icon.style.opacity = '0.78';
|
|
843
|
+
valueElement.appendChild(icon);
|
|
844
|
+
return valueElement;
|
|
845
|
+
}
|
|
846
|
+
function appendInspectorReadonlyTextValue(doc, parent, value, access, effect = 'active', title) {
|
|
847
|
+
const valueText = doc.createElement('span');
|
|
848
|
+
valueText.textContent = value;
|
|
849
|
+
valueText.style.cssText = 'min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap';
|
|
850
|
+
parent.appendChild(valueText);
|
|
851
|
+
parent.title = title ?? createInspectorStatusTitle(access, effect);
|
|
852
|
+
}
|
|
853
|
+
function createInspectorReadonlyScalarControl(doc, value, access, effect = 'active', title) {
|
|
854
|
+
const valueElement = createInspectorReadonlyValueShell(doc, access, effect, title);
|
|
855
|
+
appendInspectorReadonlyTextValue(doc, valueElement, value, access, effect, title);
|
|
856
|
+
return valueElement;
|
|
857
|
+
}
|
|
858
|
+
function createInspectorReadonlyDetailsShell(doc, property, access) {
|
|
859
|
+
const effect = getInspectorPropertyEffect(property);
|
|
860
|
+
const warningLike = effect === 'default' || effect === 'derived' || access === 'runtime' || effect === 'runtime';
|
|
861
|
+
const unsupported = effect === 'unsupported';
|
|
862
|
+
const details = doc.createElement('details');
|
|
863
|
+
details.dataset.editorInspectorAccess = access;
|
|
864
|
+
details.dataset.editorInspectorEffect = effect;
|
|
865
|
+
details.title = createInspectorStatusTitle(access, effect, property.tooltip ?? property.disabledReason);
|
|
866
|
+
details.style.cssText = [
|
|
867
|
+
'min-width:0',
|
|
868
|
+
'max-width:100%',
|
|
869
|
+
'padding:3px 6px',
|
|
870
|
+
`border:1px solid ${unsupported ? 'var(--fps-editor-danger-border)' : warningLike ? 'var(--fps-editor-warn-border)' : 'var(--fps-editor-readonly-border)'}`,
|
|
871
|
+
'border-radius:3px',
|
|
872
|
+
`background:${unsupported ? 'var(--fps-editor-danger-soft)' : warningLike ? 'var(--fps-editor-warn-soft)' : 'var(--fps-editor-readonly-bg)'}`,
|
|
873
|
+
`color:${unsupported ? 'var(--fps-editor-danger-text)' : warningLike ? 'var(--fps-editor-warn)' : 'var(--fps-editor-readonly-text)'}`,
|
|
874
|
+
].join(';');
|
|
875
|
+
return details;
|
|
876
|
+
}
|
|
585
877
|
function createInspectorInputBase(doc, target, property) {
|
|
586
878
|
const input = doc.createElement('input');
|
|
587
879
|
input.value = property.mixed ? '' : String(property.value);
|
|
@@ -609,7 +901,14 @@ function createInspectorBooleanControl(doc, target, property) {
|
|
|
609
901
|
const input = createInspectorInputBase(doc, target, property);
|
|
610
902
|
input.type = 'checkbox';
|
|
611
903
|
input.checked = property.mixed ? false : property.value === true;
|
|
612
|
-
input.style.cssText =
|
|
904
|
+
input.style.cssText = [
|
|
905
|
+
'width:16px',
|
|
906
|
+
'height:16px',
|
|
907
|
+
'accent-color:var(--fps-editor-accent)',
|
|
908
|
+
'justify-self:start',
|
|
909
|
+
'cursor:pointer',
|
|
910
|
+
'box-shadow:0 0 0 1px var(--fps-editor-editable-border)',
|
|
911
|
+
].join(';');
|
|
613
912
|
return input;
|
|
614
913
|
}
|
|
615
914
|
function createInspectorEnumControl(doc, target, property) {
|
|
@@ -658,36 +957,30 @@ function createInspectorColorControl(doc, target, property) {
|
|
|
658
957
|
const input = createInspectorInputBase(doc, target, property);
|
|
659
958
|
input.type = 'color';
|
|
660
959
|
input.value = colorValueToHex(property.value);
|
|
661
|
-
input.style.cssText = 'width:34px;height:26px;border:1px solid var(--fps-editor-border);border-radius:3px;background:
|
|
960
|
+
input.style.cssText = 'width:34px;height:26px;border:1px solid var(--fps-editor-editable-border);border-radius:3px;background:var(--fps-editor-editable-bg);padding:0;box-shadow:var(--fps-editor-editable-shadow)';
|
|
662
961
|
return input;
|
|
663
962
|
}
|
|
664
963
|
function createInspectorReadonlyControl(doc, _target, property) {
|
|
665
964
|
if (!property.mixed && isExpandableInspectorValue(property.value)) {
|
|
666
965
|
return createInspectorObjectReadonlyControl(doc, property);
|
|
667
966
|
}
|
|
668
|
-
const
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
`color:${property.persistence === 'runtime' ? 'var(--fps-editor-muted)' : 'var(--fps-editor-text)'}`,
|
|
672
|
-
'overflow:hidden',
|
|
673
|
-
'text-overflow:ellipsis',
|
|
674
|
-
'white-space:nowrap',
|
|
675
|
-
property.persistence === 'runtime' ? 'font-style:italic' : '',
|
|
676
|
-
].filter(Boolean).join(';');
|
|
677
|
-
if (property.persistence === 'runtime')
|
|
678
|
-
valueElement.title = 'Runtime-only context';
|
|
679
|
-
return valueElement;
|
|
967
|
+
const access = getInspectorPropertyAccess(property);
|
|
968
|
+
const effect = getInspectorPropertyEffect(property);
|
|
969
|
+
return createInspectorReadonlyScalarControl(doc, property.mixed ? '--' : formatLocalEditorBrowserInspectorValue(property.value), access, effect, createInspectorStatusTitle(access, effect, property.tooltip ?? property.disabledReason));
|
|
680
970
|
}
|
|
681
971
|
function createInspectorObjectReadonlyControl(doc, property) {
|
|
682
|
-
const
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
'max-width:100%',
|
|
686
|
-
`color:${property.persistence === 'runtime' ? 'var(--fps-editor-muted)' : 'var(--fps-editor-text)'}`,
|
|
687
|
-
].join(';');
|
|
972
|
+
const access = getInspectorPropertyAccess(property);
|
|
973
|
+
const effect = getInspectorPropertyEffect(property);
|
|
974
|
+
const details = createInspectorReadonlyDetailsShell(doc, property, access);
|
|
688
975
|
const summary = doc.createElement('summary');
|
|
689
|
-
summary.
|
|
690
|
-
|
|
976
|
+
summary.style.cssText = 'cursor:pointer;display:flex;align-items:center;gap:5px;overflow:hidden;white-space:nowrap';
|
|
977
|
+
const icon = createLocalEditorIcon(doc, 'lock', { size: 11, strokeWidth: 2.3 });
|
|
978
|
+
icon.style.opacity = '0.78';
|
|
979
|
+
summary.appendChild(icon);
|
|
980
|
+
const summaryText = doc.createElement('span');
|
|
981
|
+
summaryText.textContent = formatInspectorObjectSummary(property.value);
|
|
982
|
+
summaryText.style.cssText = 'min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap';
|
|
983
|
+
summary.appendChild(summaryText);
|
|
691
984
|
details.appendChild(summary);
|
|
692
985
|
const pre = doc.createElement('pre');
|
|
693
986
|
pre.textContent = formatLocalEditorBrowserInspectorValue(property.value);
|
|
@@ -698,15 +991,13 @@ function createInspectorObjectReadonlyControl(doc, property) {
|
|
|
698
991
|
'word-break:break-word',
|
|
699
992
|
'margin:6px 0 0',
|
|
700
993
|
'padding:7px',
|
|
701
|
-
|
|
994
|
+
`border:1px solid ${effect === 'unsupported' ? 'var(--fps-editor-danger-border)' : effect === 'default' || effect === 'derived' || effect === 'runtime' || access === 'runtime' ? 'var(--fps-editor-warn-border)' : 'var(--fps-editor-readonly-border)'}`,
|
|
702
995
|
'border-radius:3px',
|
|
703
996
|
'background:var(--fps-editor-field)',
|
|
704
997
|
'font-size:11px',
|
|
705
998
|
'line-height:1.45',
|
|
706
999
|
].join(';');
|
|
707
1000
|
details.appendChild(pre);
|
|
708
|
-
if (property.persistence === 'runtime')
|
|
709
|
-
details.title = 'Runtime-only context';
|
|
710
1001
|
return details;
|
|
711
1002
|
}
|
|
712
1003
|
export function applyLocalEditorBrowserInspectorControlBinding(element, target, property, options) {
|
|
@@ -718,24 +1009,38 @@ export function applyLocalEditorBrowserInspectorControlBinding(element, target,
|
|
|
718
1009
|
element.dataset.serializedValueType = property.valueType;
|
|
719
1010
|
element.dataset.serializedCommitMode = property.commitMode;
|
|
720
1011
|
element.dataset.serializedPersistence = property.persistence;
|
|
1012
|
+
element.dataset.serializedEffect = getInspectorPropertyEffect(property);
|
|
1013
|
+
if (property.disabledReason)
|
|
1014
|
+
element.dataset.serializedDisabledReason = property.disabledReason;
|
|
721
1015
|
if (options?.source)
|
|
722
1016
|
element.dataset.serializedEditSource = options.source;
|
|
723
|
-
|
|
1017
|
+
const access = getInspectorPropertyAccess(property);
|
|
1018
|
+
const effect = getInspectorPropertyEffect(property);
|
|
1019
|
+
element.title = access === 'editable' && effect === 'active'
|
|
1020
|
+
? property.tooltip ?? property.label
|
|
1021
|
+
: createInspectorStatusTitle(access, effect, property.tooltip ?? property.disabledReason ?? property.label);
|
|
724
1022
|
if ('disabled' in element) {
|
|
725
|
-
element.disabled = property
|
|
1023
|
+
element.disabled = !isInspectorPropertyEditable(property);
|
|
726
1024
|
}
|
|
727
1025
|
}
|
|
728
1026
|
function createInspectorInputStyle() {
|
|
729
1027
|
return [
|
|
730
1028
|
createEditorInputStyle(),
|
|
731
1029
|
'padding:0 6px',
|
|
1030
|
+
'background:var(--fps-editor-editable-bg)',
|
|
1031
|
+
'border-color:var(--fps-editor-editable-border)',
|
|
1032
|
+
'color:var(--fps-editor-text-strong)',
|
|
1033
|
+
'box-shadow:var(--fps-editor-editable-shadow)',
|
|
732
1034
|
].join(';');
|
|
733
1035
|
}
|
|
734
1036
|
function appendReadOnlyRow(doc, parent, label, value) {
|
|
735
|
-
const valueElement = doc
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
1037
|
+
const valueElement = createInspectorReadonlyScalarControl(doc, value, 'readonly', 'active', 'Read-only summary');
|
|
1038
|
+
const row = createPropertyRow(doc, label, valueElement);
|
|
1039
|
+
row.dataset.editorInspectorAccess = 'readonly';
|
|
1040
|
+
const labelElement = row.firstElementChild;
|
|
1041
|
+
if (labelElement)
|
|
1042
|
+
labelElement.style.cssText = createInspectorPropertyLabelStyle('readonly', 'active');
|
|
1043
|
+
parent.appendChild(row);
|
|
739
1044
|
}
|
|
740
1045
|
function createLegacyInspectorObject(serializedObject) {
|
|
741
1046
|
const targetIds = 'targetIds' in serializedObject ? serializedObject.targetIds : [serializedObject.targetId];
|