Show a placed object's struct the way its asset does

The instance inspector drew its own, flatter version of a struct: one row per
field, no bundle head. It now draws the same block as the asset side, because
there is only one implementation left - both call buildStructBlock. What the
instance does not get is everything that touches the shape: renaming a field,
'+ Field', and the per-field '×'. The shape belongs to the asset; a placed
object overrides values. The revert and override marks sit on the head rather
than on a field, since an override covers the whole bundle at once.

Underneath this, editing a single field of an unoverridden struct used to blank
its siblings: structFieldsOf filled the untouched fields from spec.read, which
is object.overrides[key] and therefore undefined until something is overridden.
structOf then handed back the empty shape. PreviewedValue carries 'current' now
- the resolved value the row actually shows - and the fields come from that.
Without it the old path stays, which is right for the asset side, whose prop
bag is dense.
This commit is contained in:
root committed 2026-09-10 23:05:02 +02:00
1 parent 14f542365e
commit 9f482573e5
9 files changed
+606 -207

No files matched your search

+97 -74
View File
@@ -41,10 +41,18 @@ import { Button, TextField } from '../../ui';
import { addCollider, createContainer, removeObjects } from '../actions';
import { aspectActions, sizeCommand } from './aspectAction';
import { defSignature } from './assetSection';
import { createFieldRow, type FieldRow, labelledRow } from './fieldFactory';
import { createFieldRow, type FieldRow, labelledRow, type RowActions } from './fieldFactory';
import { addGap, addNote, addTitle, type InspectorHost } from './host';
import type { ListSlot } from '../state';
import { parentRow } from './parentRow';
import { listOpener, type PreviewedValue, previewingHandlers, structFields } from './previewHandlers';
import {
listOpener,
type PreviewedValue,
previewingHandlers,
structFields,
structFieldsOf,
} from './previewHandlers';
import { buildStructBlock } from './structBlock';
import { currentProps, currentRef, deviations, positionMoved, targetOf, viewOf } from './objectQueries';
import { componentRow, OFFSET_AXES, offsetKey, POSITION_AXES } from './vectorRows';
@@ -309,85 +317,100 @@ function propertyRow(
const overrideSpec: PreviewedValue<ObjectRef> = {
target: () => currentRef(host.state, holderId, objectId),
read: (ref) => ref.object.overrides[def.key],
current: () => currentProps(host.state, holderId, objectId)?.[def.key],
write: (ref, value) => ref.setOverride(def.key, value),
remove: (ref) => ref.setOverride(def.key, undefined),
commit: (value) => new SetOverrideCommand(holderId, objectId, def.key, value),
};
const row = createFieldRow(
host,
def,
values[def.key] ?? def.default,
{
...previewingHandlers<ObjectRef>(host, overrideSpec),
...listOpener(host.state, def, () => ({ kind: 'object', holderId, objectId, key: def.key })),
...structFields<ObjectRef>(host, def, overrideSpec, (field) => ({
kind: 'object',
holderId,
objectId,
key: def.key,
field,
})),
const fieldSlot = (field: string): ListSlot => ({
kind: 'object',
holderId,
objectId,
key: def.key,
field,
});
const rowActions: RowActions = {
extra: aspectActions(
host,
owner,
def,
() => currentProps(host.state, holderId, objectId),
(values) =>
host.state.execute(
sizeCommand(values, (axis, value) => new SetOverrideCommand(holderId, objectId, axis, value)),
),
),
reset: () => {
const { template, asset } = bases();
return [
...(template
? [
{
label: 'Reset to container',
onSelect: () =>
host.state.execute(new RevertOverrideToTemplateCommand(holderId, objectId, def.key)),
},
]
: []),
...(asset
? [
{
label: 'Reset to asset',
onSelect: () => host.state.execute(new ClearOverrideCommand(holderId, objectId, def.key)),
},
]
: []),
];
},
{
extra: aspectActions(
host,
owner,
def,
() => currentProps(host.state, holderId, objectId),
(values) =>
host.state.execute(
sizeCommand(values, (axis, value) => new SetOverrideCommand(holderId, objectId, axis, value)),
),
),
reset: () => {
const { template, asset } = bases();
return [
...(template
? [
{
label: 'Reset to container',
onSelect: () =>
host.state.execute(new RevertOverrideToTemplateCommand(holderId, objectId, def.key)),
},
]
: []),
...(asset
? [
{
label: 'Reset to asset',
onSelect: () => host.state.execute(new ClearOverrideCommand(holderId, objectId, def.key)),
},
]
: []),
];
},
apply: () => {
const { template, applyToAsset } = bases();
return [
...(template
? [
{
label: 'Apply to container',
onSelect: () =>
host.state.execute(new ApplyOverrideToContainerCommand(holderId, objectId, def.key)),
},
]
: []),
...(applyToAsset
? [
{
label: 'Apply to asset',
onSelect: () =>
host.state.execute(new ApplyOverrideToAssetCommand(holderId, objectId, def.key)),
},
]
: []),
];
},
apply: () => {
const { template, applyToAsset } = bases();
return [
...(template
? [
{
label: 'Apply to container',
onSelect: () =>
host.state.execute(new ApplyOverrideToContainerCommand(holderId, objectId, def.key)),
},
]
: []),
...(applyToAsset
? [
{
label: 'Apply to asset',
onSelect: () =>
host.state.execute(new ApplyOverrideToAssetCommand(holderId, objectId, def.key)),
},
]
: []),
];
},
);
};
// A struct is a block of rows, which only it can add to the body; the same one the asset shows,
// minus the renaming and the field buttons – a placed object overrides values, not the shape.
const row =
def.kind === 'struct'
? buildStructBlock(
host,
def,
values[def.key] ?? def.default,
structFieldsOf<ObjectRef>(host, def, overrideSpec, fieldSlot),
{ actions: rowActions },
)
: createFieldRow(
host,
def,
values[def.key] ?? def.default,
{
...previewingHandlers<ObjectRef>(host, overrideSpec),
...listOpener(host.state, def, () => ({ kind: 'object', holderId, objectId, key: def.key })),
...structFields<ObjectRef>(host, def, overrideSpec, fieldSlot),
},
rowActions,
);
host.fields.set(def.key, row);
host.body.add(row.row);
if (def.kind !== 'struct') host.body.add(row.row);
return row;
}