import { SetParentCommand } from '../../core/commands/sceneObjectCommands'; import { findObject, isSelfOrAncestor, treeOrder } from '../../core/model/hierarchy'; import { findAsset, type ObjectHolder } from '../../core/model/Project'; import type { SceneObject } from '../../core/model/SceneObject'; import { Dropdown, Label, Menu, type MenuItem } from '../../ui'; import { labelledRow } from './fieldFactory'; import type { InspectorHost } from './host'; import { viewOf } from './objectQueries'; /** * "Parent: ▾" – picks another object of the same scene or container, or none. The object's own * subtree is excluded (that would be a cycle). For behaviors the parent is the owner. Content of a * placed container only shows its parent: where it hangs is the template's business. */ export function parentRow(host: InspectorHost, holder: ObjectHolder, object: SceneObject, fixed: boolean) { const { row } = labelledRow('Parent'); const nameOf = (o: SceneObject) => o.name || findAsset(host.state.project, o.assetId)?.name || '?'; const parent = findObject(holder, object.parentId); const name = parent ? nameOf(parent) : 'None'; if (fixed) { row.add(new Label(name, { style: 'muted' }), 1); return row; } const button = new Dropdown(name); button.onClick = () => { // Resolved anew on every open: `holder` is a derived copy of the scene, so one captured when // the row was built lists the objects of that moment, not the ones there now. const current = viewOf(host.state.project, holder.id) ?? holder; const items: MenuItem[] = [ { label: 'None', onSelect: () => host.state.execute(new SetParentCommand(holder.id, object.id, '')) }, ]; for (const { object: candidate, depth } of treeOrder(current)) { if (isSelfOrAncestor(current, object.id, candidate.id)) continue; const label = `${' '.repeat(depth)}${nameOf(candidate)}`; items.push({ label: candidate.id === object.parentId ? `● ${label}` : label, onSelect: () => host.state.execute(new SetParentCommand(holder.id, object.id, candidate.id)), }); } // Searchable: this lists every object of the scene or container, which grows with the project. new Menu(items, { searchable: true }).openBelow(button); }; row.add(button, 1); return row; }