Files
feuerwerk/src/editor/inspector/assetSection.ts
T
rootandClaude Opus 5 4d84231b9f Trim needless comments and fix the picker width
Remove comments that only restate the code, keeping the section banners
and the notes that carry a reason, a measured fact or an external
contract.

ButtonOptions.width claimed to be a fixed width but only set minWidth,
so the Graph and Texture pickers grew with the name they showed. It is
now what it promises, with a separate minWidth for the buttons that have
two forms. Narrow buttons shrink their padding, otherwise a 24px icon
button had 8px left for its glyph and truncate dropped it entirely.

__gm.labelsDropped() guards that class: the overflow guard only saw text
that was too wide, never a label truncated to nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 15:52:05 +02:00

171 lines
6.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
RenameAssetCommand,
ResetAssetPropertyCommand,
SetAssetPropertyCommand,
} from '../../core/commands/assetCommands';
import { nextVariableName } from '../../core/commands/naming';
import {
AddVariableCommand,
RemoveVariableCommand,
RenameVariableCommand,
} from '../../core/commands/variableCommands';
import type { Asset, BehaviorAsset } from '../../core/model/Asset';
import { findAsset, type Project } from '../../core/model/Project';
import {
COMMON_PROPS,
type PropertyDef,
schemaForAsset,
VARIABLE_KINDS,
type VariableKind,
} from '../../core/properties/PropertySchema';
import { isModified, resolveAssetProps } from '../../core/properties/resolve';
import { Button, Dropdown, HStack, Label, Menu, Spacer, TextField, Theme } from '../../ui';
import { createField, createFieldRow, type FieldHandlers, labelledRow } from './fieldFactory';
import { addNote, addTitle, type InspectorHost } from './host';
import { previewingHandlers } from './previewHandlers';
const VARIABLE_KIND_LABELS: Record<VariableKind, string> = {
number: 'Number',
boolean: 'Boolean',
text: 'Text',
};
// Keyed by the wide `PropertyDef['kind']` because `variables` is a `PropertyDef[]`, but only the
// three VariableKinds ever reach it – the '?' fallback is a safety net, not a case to fill in.
const VARIABLE_KIND_MARKS: Partial<Record<PropertyDef['kind'], string>> = {
number: '#',
boolean: '✓',
text: 'Aa',
};
/** Structure-defining facts of an asset selection: the schema keys and kinds. */
export function assetSignature(project: Project, assetId: string): string {
const asset = findAsset(project, assetId);
return asset
? schemaForAsset(asset)
.map((d) => `${d.key}:${d.kind}`)
.join(',')
: 'missing';
}
/** Name, properties (asset values marked when they differ from the default) and, for behaviors, the variables. */
export function buildAssetSection(host: InspectorHost, assetId: string): void {
const asset = findAsset(host.state.project, assetId);
if (!asset) return;
addTitle(host, `${asset.type.toUpperCase()} ASSET`);
host.nameField = new TextField(asset.name, {
onChange: (name) => {
if (name.trim()) host.state.execute(new RenameAssetCommand(assetId, name.trim()));
else host.nameField?.setValue(asset.name);
},
});
const { row } = labelledRow('Name');
row.add(host.nameField, 1);
host.body.add(row);
if (asset.type === 'behavior') {
buildProps(host, asset, COMMON_PROPS);
buildVariables(host, asset);
} else {
buildProps(host, asset, schemaForAsset(asset));
}
}
/** Pushes the current asset values into the rows. False when the asset is gone (rebuild). */
export function refreshAssetSection(host: InspectorHost, assetId: string): boolean {
const asset = findAsset(host.state.project, assetId);
if (!asset) return false;
host.nameField?.setValue(asset.name);
const values = resolveAssetProps(asset);
for (const [key, row] of host.fields) {
row.setValue(values[key] ?? 0);
row.setModified(isModified(asset, key));
}
return true;
}
function buildProps(host: InspectorHost, asset: Asset, schema: readonly PropertyDef[]): void {
const values = resolveAssetProps(asset);
for (const def of schema) {
const row = createFieldRow(
host,
def,
values[def.key] ?? def.default,
assetHandlers(host, asset.id, def),
() => {
const target = findAsset(host.state.project, asset.id);
if (!target || !isModified(target, def.key)) return [];
return [
{
label: 'Reset to default',
onSelect: () => host.state.execute(new ResetAssetPropertyCommand(asset.id, def.key)),
},
];
},
);
row.setModified(isModified(asset, def.key));
host.fields.set(def.key, row);
host.body.add(row.row);
}
}
/** Previews straight into the asset while editing; the command records the value from before the preview. */
function assetHandlers(host: InspectorHost, assetId: string, def: PropertyDef): FieldHandlers {
return previewingHandlers<Asset>(host, {
target: () => findAsset(host.state.project, assetId),
read: (asset) => asset.props[def.key],
write: (asset, value) => (asset.props[def.key] = value),
// No `remove`: an asset bag is dense – loading fills the defaults, a new variable brings its own.
commit: (value) => new SetAssetPropertyCommand(assetId, def.key, value),
});
}
/** Variables of a behavior: kind mark, name, default value, remove – plus an "add" menu. */
function buildVariables(host: InspectorHost, asset: BehaviorAsset): void {
const header = new HStack({ gap: Theme.gap, align: 'center' });
header.add(new Label('VARIABLES', { style: 'title' }), 1);
const add = new Dropdown('+ Variable', { variant: 'ghost' });
add.onClick = () => {
const items = VARIABLE_KINDS.map((kind) => ({
label: VARIABLE_KIND_LABELS[kind],
onSelect: () => {
const current = findAsset(host.state.project, asset.id);
if (current?.type === 'behavior')
host.state.execute(new AddVariableCommand(asset.id, nextVariableName(current), kind));
},
}));
new Menu(items).openBelow(add);
};
header.add(add);
host.body.add(header);
if (asset.variables.length === 0) addNote(host, 'No variables yet.');
const values = resolveAssetProps(asset);
for (const def of asset.variables) {
const row = new HStack({ gap: Theme.gap, align: 'center' });
row.minHeight = Theme.rowHeight;
const mark = new Label(VARIABLE_KIND_MARKS[def.kind] ?? '?', { style: 'small', align: 'center' });
mark.minWidth = 18;
row.add(mark);
row.add(
new TextField(def.key, {
width: 90,
onChange: (next) => {
host.state.execute(new RenameVariableCommand(asset.id, def.key, next.trim()));
},
}),
);
const field = createField(host, def, values[def.key] ?? def.default, assetHandlers(host, asset.id, def));
row.add(field.widget, field.grow ? 1 : 0);
if (!field.grow) row.add(new Spacer(), 1);
row.add(
new Button('×', {
variant: 'ghost',
width: Theme.controlHeight,
onClick: () => host.state.execute(new RemoveVariableCommand(asset.id, def.key)),
}),
);
host.fields.set(def.key, { row, setValue: field.setValue, setModified: () => {} });
host.body.add(row);
}
}