Split the object inspector, and sweep out the section banners

objectSection.ts had grown to 559 lines around three separable parts: the
queries, the vector rows and the parent row. Four object commands repeated the
same capture/restore preamble – applyCommands stays out, its five carry extra
undo state whose restore order matters.

58 dashed banner comments are gone. The nine labels in nodes/index.ts stay:
they are the only structure a flat 90-entry array has.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Opus 5 committed 2026-09-10 07:14:45 +02:00
1 parent 11981b40d0
commit 3b3f374e10
11 files changed
+258 -244

No files matched your search

+62 -50
View File
@@ -1,6 +1,12 @@
import { newId } from '../ids';
import { isSelfOrAncestor, localPositionFor, removeSubtrees, worldPosition } from '../model/hierarchy';
import { captureObject, isLiveInstance, type ObjectMemo, restoreObject } from '../model/instance';
import {
captureObject,
isLiveInstance,
type ObjectMemo,
type ObjectRef,
restoreObject,
} from '../model/instance';
import { placementRejection } from '../model/placement';
import { type Position, type PropertyValue, toPosition, type Vec2 } from '../model/Properties';
import type { Project } from '../model/Project';
@@ -17,6 +23,33 @@ import {
requireStructural,
} from './require';
/**
* An edit to one placed object. The base captures the object before the edit and puts it back on
* undo, so a subclass only says what changes. `applyCommands` deliberately does not use this: its
* commands carry extra undo state whose restore order matters.
*/
abstract class ObjectEditCommand implements Command {
abstract readonly label: string;
private memo: ObjectMemo | undefined;
protected constructor(
private readonly holderId: string,
private readonly objectId: string,
) {}
protected abstract apply(ref: ObjectRef, project: Project): void;
execute(project: Project): void {
const ref = requireObjectRef(project, this.holderId, this.objectId);
this.memo = captureObject(ref);
this.apply(ref, project);
}
undo(): void {
if (this.memo) restoreObject(this.memo);
}
}
/**
* The position is a world position; with a parent it is converted to the parent's local space.
* Placing a container adds the container object alone – its content is resolved from the template
@@ -88,91 +121,70 @@ export class RemoveSceneObjectCommand extends RemoveSceneObjectsCommand {
* The previous position is read at execution time, so a caller that previewed the move must
* restore the model before executing.
*/
export class MoveSceneObjectCommand implements Command {
export class MoveSceneObjectCommand extends ObjectEditCommand {
readonly label = 'Move object';
private memo: ObjectMemo | undefined;
constructor(
private readonly holderId: string,
private readonly objectId: string,
holderId: string,
objectId: string,
private readonly position: Position,
) {}
execute(project: Project): void {
const ref = requireObjectRef(project, this.holderId, this.objectId);
this.memo = captureObject(ref);
ref.setPosition(this.position);
) {
super(holderId, objectId);
}
undo(): void {
if (this.memo) restoreObject(this.memo);
protected override apply(ref: ObjectRef): void {
ref.setPosition(this.position);
}
}
/** Editor-only: undoable like any other edit, but it changes nothing the game can see. */
export class SetEditorOffsetCommand implements Command {
export class SetEditorOffsetCommand extends ObjectEditCommand {
readonly label = 'Editor offset';
private memo: ObjectMemo | undefined;
constructor(
private readonly holderId: string,
private readonly objectId: string,
holderId: string,
objectId: string,
private readonly offset: Vec2,
) {}
) {
super(holderId, objectId);
}
execute(project: Project): void {
const ref = requireObjectRef(project, this.holderId, this.objectId);
this.memo = captureObject(ref);
protected override apply(ref: ObjectRef): void {
ref.setEditorOffset(this.offset);
}
undo(): void {
if (this.memo) restoreObject(this.memo);
}
}
export class RenameSceneObjectCommand implements Command {
export class RenameSceneObjectCommand extends ObjectEditCommand {
readonly label = 'Rename object';
private memo: ObjectMemo | undefined;
constructor(
private readonly holderId: string,
private readonly objectId: string,
holderId: string,
objectId: string,
private readonly name: string,
) {}
execute(project: Project): void {
const ref = requireObjectRef(project, this.holderId, this.objectId);
this.memo = captureObject(ref);
ref.setName(this.name);
) {
super(holderId, objectId);
}
undo(): void {
if (this.memo) restoreObject(this.memo);
protected override apply(ref: ObjectRef): void {
ref.setName(this.name);
}
}
abstract class OverrideCommand implements Command {
private memo: ObjectMemo | undefined;
abstract class OverrideCommand extends ObjectEditCommand {
protected constructor(
private readonly holderId: string,
private readonly objectId: string,
holderId: string,
objectId: string,
protected readonly key: string,
readonly label: string,
) {}
) {
super(holderId, objectId);
}
protected abstract nextValue(object: SceneObject, project: Project): PropertyValue | undefined;
execute(project: Project): void {
const ref = requireObjectRef(project, this.holderId, this.objectId);
this.memo = captureObject(ref);
protected override apply(ref: ObjectRef, project: Project): void {
ref.setOverride(this.key, this.nextValue(ref.object, project));
}
undo(): void {
if (this.memo) restoreObject(this.memo);
}
}
export class SetOverrideCommand extends OverrideCommand {
-6
View File
@@ -3,8 +3,6 @@ import type { PropertyDef } from '../../properties/PropertySchema';
import type { NodeDef, PortDef } from '../NodeRegistry';
import { asBoolean, asNumber } from '../values';
// --- ports -----------------------------------------------------------------------------------
export const exec = (key = 'exec', label = ''): PortDef => ({ key, label, type: 'exec' });
export const num = (key: string, label = key): PortDef => ({ key, label, type: 'number' });
export const bool = (key: string, label = key): PortDef => ({ key, label, type: 'boolean' });
@@ -23,8 +21,6 @@ export const collisionOutputs = (): PortDef[] => [
obj('collider'),
];
// --- params ----------------------------------------------------------------------------------
export const keyParam: PropertyDef = { key: 'key', label: 'Key', kind: 'key', default: 'ArrowRight' };
/** Options are empty on purpose: the graph inspector fills in the behavior's variables. */
@@ -39,8 +35,6 @@ export const variableParam: PropertyDef = {
/** No `assetTypes`: any placeable asset is a valid answer here. */
export const assetParam: PropertyDef = { key: 'asset', label: 'Asset', kind: 'asset', default: '' };
// --- node factories --------------------------------------------------------------------------
export function binaryMath(type: string, description: string, fn: (a: number, b: number) => number): NodeDef {
return {
type,