Describe every value kind in one table instead of spreading it over the code
A kind like colour or texture used to be spelled out in five parallel lists, its port type, its empty value, its glyph, its label and its list tag - 22 literal places for texture alone. core/properties/kinds.ts now holds one row per kind and the rest is derived from it, so adding a kind is one row. The five literal nodes collapse into literals.ts, the six kind menus into one helper. Number, Text, Boolean is now the one order everywhere; the variable and port menus used to list Boolean before Text. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8a7185948d
commit
3ed8b68ecf
39 files changed
+699
-565
No files matched your search
@@ -17,11 +17,9 @@ import {
|
||||
} from '../../core/graph';
|
||||
import type { Project } from '../../core/model/Project';
|
||||
import { isList, type PropertyValue } from '../../core/model/Properties';
|
||||
import { assetRefLabel } from '../../core/properties/assetRef';
|
||||
import type { PropertyDef } from '../../core/properties/PropertySchema';
|
||||
import { textureRefLabel } from '../../core/properties/textureRef';
|
||||
import { formatNumber, Theme, textStyle, type TextStyleToken, toHex, truncate, Widget } from '../../ui';
|
||||
import { keyLabel } from '../inspector/fieldFactory';
|
||||
import { keyLabel, referenceLabel } from '../inspector/fieldFactory';
|
||||
import { collapsedNodeWidth } from './folding';
|
||||
|
||||
/** Node geometry in graph units; exported because edge anchors must land exactly on these ports. */
|
||||
@@ -431,11 +429,9 @@ function formatParam(param: PropertyDef, value: PropertyValue | undefined, proje
|
||||
/** A value in the words of the field that edits it: a key's name, a color's hex, a texture's name. */
|
||||
function formatField(def: PropertyDef, value: Value, project: Project | null): string {
|
||||
if (def.kind === 'key') return keyLabel(asString(value));
|
||||
if (def.kind === 'asset') return assetRefLabel(project, asString(value));
|
||||
if (def.kind === 'texture') return textureRefLabel(project, asString(value));
|
||||
if (def.kind === 'color') return toHex(asNumber(value));
|
||||
if (def.kind === 'enum') return def.options.find((o) => o.value === value)?.label ?? formatValue(value);
|
||||
return formatValue(value);
|
||||
return referenceLabel(def, project, asString(value)) ?? formatValue(value);
|
||||
}
|
||||
|
||||
function formatLive(output: PortDef, value: Value, project: Project | null): string {
|
||||
|
||||
@@ -15,20 +15,15 @@ import {
|
||||
import type { Command } from '../../core/commands/Command';
|
||||
import type { Asset, BehaviorAsset } from '../../core/model/Asset';
|
||||
import { findAsset, type Project } from '../../core/model/Project';
|
||||
import {
|
||||
type PropertyDef,
|
||||
schemaForAsset,
|
||||
type StructFieldDef,
|
||||
type StructFieldKind,
|
||||
VARIABLE_KINDS,
|
||||
} from '../../core/properties/PropertySchema';
|
||||
import { type StructFieldKind, VARIABLE_KINDS } from '../../core/properties/kinds';
|
||||
import { type PropertyDef, schemaForAsset, type StructFieldDef } from '../../core/properties/PropertySchema';
|
||||
import { markedAssetSchema } from '../../core/properties/deviation';
|
||||
import { isModified, resolveAssetProps } from '../../core/properties/resolve';
|
||||
import { Dropdown, HStack, Label, Menu, TextField, Theme } from '../../ui';
|
||||
import { aspectActions, sizeCommand } from './aspectAction';
|
||||
import { createFieldRow, type FieldHandlers, labelledRow } from './fieldFactory';
|
||||
import { addNote, addTitle, type InspectorHost } from './host';
|
||||
import { buildVariable, KIND_LABELS, type VariableEditing } from './variableRows';
|
||||
import { buildVariable, kindMenu, type VariableEditing } from './variableRows';
|
||||
import { listOpener, previewingHandlers, structFields } from './previewHandlers';
|
||||
import { assetValue } from './valueSpecs';
|
||||
|
||||
@@ -137,14 +132,11 @@ function buildVariables(host: InspectorHost, asset: BehaviorAsset): void {
|
||||
header.add(new Label('VARIABLES', { style: 'title' }), 1);
|
||||
const add = new Dropdown('+ Variable', { variant: 'ghost' });
|
||||
add.onClick = () => {
|
||||
const items = VARIABLE_KINDS.map((kind) => ({
|
||||
label: 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));
|
||||
},
|
||||
}));
|
||||
const items = kindMenu(VARIABLE_KINDS, (kind) => {
|
||||
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);
|
||||
|
||||
@@ -6,9 +6,10 @@ import {
|
||||
assetRefLabel,
|
||||
NO_ASSET_LABEL,
|
||||
} from '../../core/properties/assetRef';
|
||||
import { isKindWith, type ReferenceKind } from '../../core/properties/kinds';
|
||||
import {
|
||||
emptyFieldValue,
|
||||
type FieldKind,
|
||||
flatDef,
|
||||
type PropertyDef,
|
||||
type StructFieldDef,
|
||||
} from '../../core/properties/PropertySchema';
|
||||
@@ -202,29 +203,70 @@ export function removeButton(onClick: () => void): Button {
|
||||
|
||||
export const READ_ONLY: FieldHandlers = { onInput: () => {}, onChange: () => {} };
|
||||
|
||||
/** A primitive as a property definition, so one widget set serves a list item and a struct field. */
|
||||
export function primitiveDef(key: string, kind: FieldKind, label = key): PrimitiveDef {
|
||||
switch (kind) {
|
||||
case 'number':
|
||||
return { key, label, kind: 'number', default: 0 };
|
||||
case 'text':
|
||||
return { key, label, kind: 'text', default: '' };
|
||||
case 'boolean':
|
||||
return { key, label, kind: 'boolean', default: false };
|
||||
case 'color':
|
||||
return { key, label, kind: 'color', default: 0 };
|
||||
case 'texture':
|
||||
return { key, label, kind: 'texture', default: '' };
|
||||
}
|
||||
}
|
||||
|
||||
export type PrimitiveDef = Extract<PropertyDef, { kind: FieldKind }>;
|
||||
|
||||
/** A struct field as a property definition – a list field becomes the same row a list property has. */
|
||||
export function fieldDef(field: StructFieldDef, label = field.key): PropertyDef {
|
||||
return field.kind === 'list'
|
||||
? { key: field.key, label, kind: 'list', default: [] }
|
||||
: primitiveDef(field.key, field.kind, label);
|
||||
: flatDef(field.key, field.kind, { label });
|
||||
}
|
||||
|
||||
/** What a reference kind names in the project: how an id reads, and what its picker may offer. */
|
||||
interface Reference {
|
||||
none: string;
|
||||
label(project: Project | null, id: string): string;
|
||||
choices(project: Project, def: PropertyDef): { id: string; label: string }[];
|
||||
}
|
||||
|
||||
const REFERENCES: Record<ReferenceKind, Reference> = {
|
||||
asset: {
|
||||
none: NO_ASSET_LABEL,
|
||||
label: assetRefLabel,
|
||||
choices: (project, def) =>
|
||||
assetRefChoices(project, def.kind === 'asset' ? def.assetTypes : undefined).map((asset) => ({
|
||||
id: asset.id,
|
||||
label: assetChoiceLabel(asset),
|
||||
})),
|
||||
},
|
||||
texture: { none: NO_TEXTURE_LABEL, label: textureRefLabel, choices: textureRefChoices },
|
||||
};
|
||||
|
||||
/** An id in the words of its reference kind; nothing for a kind that names nothing. */
|
||||
export function referenceLabel(def: PropertyDef, project: Project | null, id: string): string | undefined {
|
||||
return isKindWith('reference', def.kind) ? REFERENCES[def.kind].label(project, id) : undefined;
|
||||
}
|
||||
|
||||
function referenceField(
|
||||
ctx: FieldContext,
|
||||
def: PropertyDef,
|
||||
ref: Reference,
|
||||
value: PropertyValue,
|
||||
handlers: FieldHandlers,
|
||||
): Field {
|
||||
// The current id is kept here rather than read back off the label: the label is a name, two
|
||||
// assets may share one (a frame's label repeats its sheet's), and a dangling reference has none.
|
||||
let current = textOf(value);
|
||||
const button = new Dropdown(ref.label(ctx.project(), current));
|
||||
button.onClick = () => {
|
||||
const mark = (id: string, text: string) => (id === current ? `● ${text}` : text);
|
||||
const items: MenuItem[] = [
|
||||
{ label: mark('', ref.none), onSelect: () => handlers.onChange('') },
|
||||
// Built on click, not once per row: assets are created, renamed and deleted while the
|
||||
// inspector stands. Searchable: one sliced sheet alone contributes an entry per frame.
|
||||
...ref.choices(ctx.project(), def).map((choice) => ({
|
||||
label: mark(choice.id, choice.label),
|
||||
onSelect: () => handlers.onChange(choice.id),
|
||||
})),
|
||||
];
|
||||
new Menu(items, { searchable: true }).openBelow(button);
|
||||
};
|
||||
return {
|
||||
widget: button,
|
||||
grow: true,
|
||||
setValue: (v) => {
|
||||
current = textOf(v);
|
||||
button.setText(ref.label(ctx.project(), current));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** One field out of a bundle; a field the value does not carry reads as the empty one of its kind. */
|
||||
@@ -308,60 +350,9 @@ export function createField(
|
||||
const button = new KeyCaptureButton(textOf(value), (key) => handlers.onChange(key));
|
||||
return { widget: button, grow: true, setValue: (v) => button.setKey(textOf(v)) };
|
||||
}
|
||||
case 'asset': {
|
||||
// The current id is kept here rather than read back off the label: the label is a name,
|
||||
// two assets may share one, and a dangling reference has no name at all.
|
||||
let current = textOf(value);
|
||||
const button = new Dropdown(assetRefLabel(ctx.project(), current));
|
||||
button.onClick = () => {
|
||||
const mark = (id: string, text: string) => (id === current ? `● ${text}` : text);
|
||||
const items: MenuItem[] = [
|
||||
{ label: mark('', NO_ASSET_LABEL), onSelect: () => handlers.onChange('') },
|
||||
// Built on click, not once per row: assets are created, renamed and deleted while
|
||||
// the inspector stands.
|
||||
...assetRefChoices(ctx.project(), def.assetTypes).map((asset) => ({
|
||||
label: mark(asset.id, assetChoiceLabel(asset)),
|
||||
onSelect: () => handlers.onChange(asset.id),
|
||||
})),
|
||||
];
|
||||
// Searchable: the list grows with the project, like the parent picker.
|
||||
new Menu(items, { searchable: true }).openBelow(button);
|
||||
};
|
||||
return {
|
||||
widget: button,
|
||||
grow: true,
|
||||
setValue: (v) => {
|
||||
current = textOf(v);
|
||||
button.setText(assetRefLabel(ctx.project(), current));
|
||||
},
|
||||
};
|
||||
}
|
||||
case 'texture': {
|
||||
// Kept for the same reason as in the asset case, and more urgently: a frame's label
|
||||
// repeats its sheet's name, so several entries can read identically.
|
||||
let current = textOf(value);
|
||||
const button = new Dropdown(textureRefLabel(ctx.project(), current));
|
||||
button.onClick = () => {
|
||||
const mark = (id: string, text: string) => (id === current ? `● ${text}` : text);
|
||||
const items: MenuItem[] = [
|
||||
{ label: mark('', NO_TEXTURE_LABEL), onSelect: () => handlers.onChange('') },
|
||||
...textureRefChoices(ctx.project()).map((choice) => ({
|
||||
label: mark(choice.id, choice.label),
|
||||
onSelect: () => handlers.onChange(choice.id),
|
||||
})),
|
||||
];
|
||||
// Searchable: one sliced sheet alone contributes an entry per frame.
|
||||
new Menu(items, { searchable: true }).openBelow(button);
|
||||
};
|
||||
return {
|
||||
widget: button,
|
||||
grow: true,
|
||||
setValue: (v) => {
|
||||
current = textOf(v);
|
||||
button.setText(textureRefLabel(ctx.project(), current));
|
||||
},
|
||||
};
|
||||
}
|
||||
case 'asset':
|
||||
case 'texture':
|
||||
return referenceField(ctx, def, REFERENCES[def.kind], value, handlers);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
} from '../../core/commands/portCommands';
|
||||
import {
|
||||
BEHAVIOR_START,
|
||||
DECLARED_PORT_KINDS,
|
||||
type DeclaredPort,
|
||||
type GraphNode,
|
||||
isPortOf,
|
||||
@@ -26,7 +25,8 @@ import {
|
||||
} from '../../core/graph';
|
||||
import { findAssetOf } from '../../core/model/Project';
|
||||
import { textOf } from '../../core/model/Properties';
|
||||
import { type PropertyDef, schemaForAsset, VARIABLE_KINDS } from '../../core/properties/PropertySchema';
|
||||
import { DECLARED_PORT_KINDS, kindMark, VARIABLE_KINDS } from '../../core/properties/kinds';
|
||||
import { type PropertyDef, schemaForAsset } from '../../core/properties/PropertySchema';
|
||||
import {
|
||||
Checkbox,
|
||||
Dropdown,
|
||||
@@ -41,7 +41,7 @@ import {
|
||||
} from '../../ui';
|
||||
import { NAME_WIDTH, removeButton } from './fieldFactory';
|
||||
import { addNote, type InspectorHost } from './host';
|
||||
import { KIND_LABELS, kindMark, markLabel } from './variableRows';
|
||||
import { kindMenu, markLabel } from './variableRows';
|
||||
|
||||
/**
|
||||
* The ports of a Variables node: each one *is* a variable of the behavior, so it is picked from that
|
||||
@@ -121,13 +121,11 @@ function buildPickedPortSection(
|
||||
onSelect: () => onPick(portOf(def)),
|
||||
}));
|
||||
const typed = (): MenuItem[] =>
|
||||
VARIABLE_KINDS.map((kind) => ({
|
||||
label: KIND_LABELS[kind],
|
||||
onSelect: () =>
|
||||
host.state.execute(
|
||||
new AddPortCommand(assetId, node.id, { key: nextPortName(node, noun.toLowerCase()), kind }),
|
||||
),
|
||||
}));
|
||||
kindMenu(VARIABLE_KINDS, (kind) =>
|
||||
host.state.execute(
|
||||
new AddPortCommand(assetId, node.id, { key: nextPortName(node, noun.toLowerCase()), kind }),
|
||||
),
|
||||
);
|
||||
const searchable = noun === 'Property';
|
||||
|
||||
const header = new HStack({ gap: Theme.gap, align: 'center' });
|
||||
@@ -189,11 +187,9 @@ export function buildPortSection(
|
||||
if (noun === 'Field') addShapePicker(host, assetId, node, variables, header);
|
||||
const add = new Dropdown(`+ ${noun}`, { variant: 'ghost' });
|
||||
add.onClick = () => {
|
||||
const items = DECLARED_PORT_KINDS.map((kind) => ({
|
||||
label: KIND_LABELS[kind],
|
||||
onSelect: () =>
|
||||
host.state.execute(new AddPortCommand(assetId, node.id, { key: nextPortName(node, base), kind })),
|
||||
}));
|
||||
const items = kindMenu(DECLARED_PORT_KINDS, (kind) =>
|
||||
host.state.execute(new AddPortCommand(assetId, node.id, { key: nextPortName(node, base), kind })),
|
||||
);
|
||||
new Menu(items).openBelow(add);
|
||||
};
|
||||
header.add(add);
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
import type { DeclaredPortKind } from '../../core/graph';
|
||||
import type { PropertyValue } from '../../core/model/Properties';
|
||||
import {
|
||||
type ListItemKind,
|
||||
type PropertyDef,
|
||||
STRUCT_FIELD_KINDS,
|
||||
type StructDef,
|
||||
type StructFieldDef,
|
||||
type StructFieldKind,
|
||||
type VariableKind,
|
||||
} from '../../core/properties/PropertySchema';
|
||||
import { kindLabel, kindMark, STRUCT_FIELD_KINDS, type StructFieldKind } from '../../core/properties/kinds';
|
||||
import type { PropertyDef, StructDef, StructFieldDef } from '../../core/properties/PropertySchema';
|
||||
import {
|
||||
Button,
|
||||
Dropdown,
|
||||
@@ -38,34 +30,9 @@ import {
|
||||
} from './fieldFactory';
|
||||
import { addNote, type InspectorHost } from './host';
|
||||
|
||||
type ShownKind = VariableKind | ListItemKind | StructFieldKind | DeclaredPortKind;
|
||||
|
||||
/** One glyph and one name per kind, wherever a kind is shown: variables, list items, fields, ports. */
|
||||
const KIND_MARKS: Record<ShownKind, string> = {
|
||||
number: '#',
|
||||
boolean: '✓',
|
||||
text: 'Aa',
|
||||
color: '◧',
|
||||
texture: '▦',
|
||||
list: '≡',
|
||||
struct: '⊞',
|
||||
object: '◇',
|
||||
};
|
||||
|
||||
export const KIND_LABELS: Record<ShownKind, string> = {
|
||||
number: 'Number',
|
||||
boolean: 'Boolean',
|
||||
text: 'Text',
|
||||
color: 'Color',
|
||||
texture: 'Texture',
|
||||
list: 'List',
|
||||
struct: 'Struct',
|
||||
object: 'Object',
|
||||
};
|
||||
|
||||
/** Variables are `PropertyDef`s, so the kind is typed wide; only the shown kinds ever reach it. */
|
||||
export function kindMark(kind: string): string {
|
||||
return (KIND_MARKS as Partial<Record<string, string>>)[kind] ?? '?';
|
||||
/** The menu every "add a kind" button opens: one entry per kind, named as the kind table names it. */
|
||||
export function kindMenu<K extends string>(kinds: readonly K[], pick: (kind: K) => void): MenuItem[] {
|
||||
return kinds.map((kind) => ({ label: kindLabel(kind), onSelect: () => pick(kind) }));
|
||||
}
|
||||
|
||||
/** Changing a declaration – offered only on the asset that declares it. */
|
||||
@@ -149,7 +116,7 @@ function structFieldRows(
|
||||
|
||||
function fieldRow(field: StructFieldDef, edit: VariableEditing | undefined): HStack {
|
||||
const name = nameCell(field.key, 1, edit && ((next) => edit.renameField(field, next)));
|
||||
return gridRow(1, markLabel(KIND_MARKS[field.kind]), name);
|
||||
return gridRow(1, markLabel(kindMark(field.kind)), name);
|
||||
}
|
||||
|
||||
/** Indent, mark and name – sized so that whatever follows starts on the value column at any depth. */
|
||||
@@ -198,10 +165,6 @@ export function markPicker(glyph: string, items: MenuItem[]): Button {
|
||||
|
||||
function addFieldMenu(kinds: readonly StructFieldKind[], edit: VariableEditing): Dropdown {
|
||||
const add = new Dropdown('+ Field', { variant: 'ghost' });
|
||||
add.onClick = () => {
|
||||
new Menu(
|
||||
kinds.map((kind) => ({ label: KIND_LABELS[kind], onSelect: () => edit.addField(kind) })),
|
||||
).openBelow(add);
|
||||
};
|
||||
add.onClick = () => new Menu(kindMenu(kinds, (kind) => edit.addField(kind))).openBelow(add);
|
||||
return add;
|
||||
}
|
||||
@@ -18,33 +18,39 @@ import {
|
||||
VStack,
|
||||
type Widget,
|
||||
} from '../../ui';
|
||||
import { type FieldValue, type ListValue, type PropertyValue, textOf } from '../../core/model/Properties';
|
||||
import {
|
||||
type FieldValue,
|
||||
type ListValue,
|
||||
type PropertyValue,
|
||||
textOf,
|
||||
untag,
|
||||
} from '../../core/model/Properties';
|
||||
import { nextFieldName } from '../../core/commands/naming';
|
||||
import {
|
||||
FIELD_KINDS,
|
||||
type FieldKind,
|
||||
kindMark,
|
||||
LIST_ITEM_KINDS,
|
||||
type ListItemKind,
|
||||
} from '../../core/properties/PropertySchema';
|
||||
} from '../../core/properties/kinds';
|
||||
import {
|
||||
type ListItem,
|
||||
itemKindOf,
|
||||
itemValue,
|
||||
makeFlatItem,
|
||||
makeItem,
|
||||
structItemFields,
|
||||
structItemOf,
|
||||
} from '../../core/properties/listItems';
|
||||
import { flatDef } from '../../core/properties/PropertySchema';
|
||||
import {
|
||||
addValue,
|
||||
createField,
|
||||
type FieldContext,
|
||||
NAME_WIDTH,
|
||||
primitiveDef,
|
||||
READ_ONLY,
|
||||
removeButton,
|
||||
} from '../inspector/fieldFactory';
|
||||
import { KIND_LABELS, kindMark, markPicker } from '../inspector/variableRows';
|
||||
import { kindMenu, markPicker } from '../inspector/variableRows';
|
||||
import type { EditorState } from '../state';
|
||||
import { type ListAccess, resolveList } from './slot';
|
||||
|
||||
@@ -103,9 +109,7 @@ export class ListEditor extends VStack {
|
||||
// Built on click, not once: which kinds fit depends on the list the tab is showing.
|
||||
this.addButton.onClick = () => {
|
||||
const kinds = this.access()?.kinds ?? LIST_ITEM_KINDS;
|
||||
new Menu(
|
||||
kinds.map((kind) => ({ label: KIND_LABELS[kind], onSelect: () => this.addItem(kind) })),
|
||||
).openBelow(this.addButton);
|
||||
new Menu(kindMenu(kinds, (kind) => this.addItem(kind))).openBelow(this.addButton);
|
||||
};
|
||||
footer.add(this.addButton);
|
||||
footer.add(new Spacer(), 1);
|
||||
@@ -210,13 +214,11 @@ export class ListEditor extends VStack {
|
||||
row.add(label);
|
||||
const mark = markPicker(
|
||||
kindMark(kind),
|
||||
access.kinds
|
||||
.filter((next) => next !== kind)
|
||||
.map((next) => ({
|
||||
label: KIND_LABELS[next],
|
||||
// Through `itemValue`, so a color keeps its number when it becomes one.
|
||||
onSelect: () => this.replaceItem(access, index, makeItem(next, itemValue(item))),
|
||||
})),
|
||||
// Through `untag`, so a color keeps its number when it becomes one.
|
||||
kindMenu(
|
||||
access.kinds.filter((next) => next !== kind),
|
||||
(next) => this.replaceItem(access, index, makeItem(next, untag(item))),
|
||||
),
|
||||
);
|
||||
if (tall) mark.minHeight = Theme.rowHeight;
|
||||
row.add(mark);
|
||||
@@ -224,12 +226,12 @@ export class ListEditor extends VStack {
|
||||
else {
|
||||
const field = createField(
|
||||
this.ctx,
|
||||
primitiveDef(String(index), kind),
|
||||
itemValue(item) ?? '',
|
||||
flatDef(String(index), kind),
|
||||
untag(item) ?? '',
|
||||
access.itemHandlers(index),
|
||||
);
|
||||
addValue(row, field);
|
||||
this.setters.push((next) => field.setValue(itemValue(next) ?? ''));
|
||||
this.setters.push((next) => field.setValue(untag(next) ?? ''));
|
||||
this.rowWidgets.push(rowWidgets(mark, field.widget));
|
||||
}
|
||||
row.add(removeButton(() => access.replace(access.value.filter((_, i) => i !== index))));
|
||||
@@ -255,14 +257,14 @@ export class ListEditor extends VStack {
|
||||
line.minHeight = Theme.rowHeight;
|
||||
const fieldMark = markPicker(
|
||||
kindMark(field.kind),
|
||||
FIELD_KINDS.filter((next) => next !== field.kind).map((next) => ({
|
||||
label: KIND_LABELS[next],
|
||||
onSelect: () =>
|
||||
kindMenu(
|
||||
FIELD_KINDS.filter((next) => next !== field.kind),
|
||||
(next) =>
|
||||
this.replaceItem(access, index, {
|
||||
...bundle,
|
||||
[field.key]: makeFlatItem(next, itemValue(bundle[field.key])),
|
||||
[field.key]: makeFlatItem(next, untag(bundle[field.key])),
|
||||
}),
|
||||
})),
|
||||
),
|
||||
);
|
||||
const name = nameField(field.key, (to) => {
|
||||
if (to && to !== field.key) this.replaceItem(access, index, renamedField(bundle, field.key, to));
|
||||
@@ -271,8 +273,8 @@ export class ListEditor extends VStack {
|
||||
line.add(name);
|
||||
const sub = createField(
|
||||
this.ctx,
|
||||
primitiveDef(field.key, field.kind),
|
||||
itemValue(bundle[field.key]) ?? '',
|
||||
flatDef(field.key, field.kind),
|
||||
untag(bundle[field.key]) ?? '',
|
||||
handlers.structFieldHandlers?.(field) ?? READ_ONLY,
|
||||
);
|
||||
setters.set(field.key, addValue(line, sub));
|
||||
@@ -290,7 +292,7 @@ export class ListEditor extends VStack {
|
||||
row.add(stack, 1);
|
||||
this.setters.push((next) => {
|
||||
const now = structItemOf(next);
|
||||
for (const field of fields) setters.get(field.key)?.(itemValue(now[field.key]) ?? '');
|
||||
for (const field of fields) setters.get(field.key)?.(untag(now[field.key]) ?? '');
|
||||
});
|
||||
this.rowWidgets.push(widgets);
|
||||
}
|
||||
@@ -319,11 +321,7 @@ function nameField(key: string, rename: (next: string) => void): TextField {
|
||||
|
||||
function addFieldMenu(add: (kind: FieldKind) => void): Dropdown {
|
||||
const button = new Dropdown('+ Field', { variant: 'ghost' });
|
||||
button.onClick = () => {
|
||||
new Menu(FIELD_KINDS.map((kind) => ({ label: KIND_LABELS[kind], onSelect: () => add(kind) }))).openBelow(
|
||||
button,
|
||||
);
|
||||
};
|
||||
button.onClick = () => new Menu(kindMenu(FIELD_KINDS, add)).openBelow(button);
|
||||
return button;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,15 +10,9 @@ import { behaviorLookup } from '../../core/model/behaviorPrograms';
|
||||
import type { ObjectRef } from '../../core/model/instance';
|
||||
import { isList, type ListValue, type PropertyValue, type StructValue } from '../../core/model/Properties';
|
||||
import { findAsset } from '../../core/model/Project';
|
||||
import { FIELD_KINDS, LIST_ITEM_KINDS, type ListItemKind } from '../../core/properties/kinds';
|
||||
import { fieldLike, type ListItem, itemLike, structItemOf } from '../../core/properties/listItems';
|
||||
import {
|
||||
FIELD_KINDS,
|
||||
LIST_ITEM_KINDS,
|
||||
type ListItemKind,
|
||||
type PropertyDef,
|
||||
schemaForAsset,
|
||||
structOf,
|
||||
} from '../../core/properties/PropertySchema';
|
||||
import { type PropertyDef, schemaForAsset, structOf } from '../../core/properties/PropertySchema';
|
||||
import { resolveAssetProps } from '../../core/properties/resolve';
|
||||
import type { FieldHandlers } from '../inspector/fieldFactory';
|
||||
import { currentProps, currentRef } from '../inspector/objectQueries';
|
||||
|
||||
Reference in new issue
Block a user