Add ESLint and Prettier, and close the node test gaps
Tooling: - Prettier as the single formatting authority (110 columns, single quotes, measured against the existing style); Markdown excluded because table-cell padding turns the README glossary into 1000-character lines. - Type-aware ESLint ending in eslint-config-prettier, so a rule always means "this is a bug", never "this looks different". - .editorconfig and an `npm run check` that runs typecheck, lint, format and tests in one go. Findings fixed on the first run: three more duplicate imports, four String(unknown) calls (parameter types narrowed instead of casts), two dead initialisers, two redundant casts, a console.log for a real error. Two warnings were false here and are disabled with a reason in place. Tests 132 -> 269: - node-catalog.test.ts covers every node with a run(), with a guard that fails when a new node has no row. - behavior-events.test.ts covers the ten event nodes through a running World, checking timing rather than existence. - Input's state machine (press/release/releaseAll) is reachable without DOM events now; the DOM handlers are thin adapters. Compare fell back to == without a configured operator although the schema default is <; aligned. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f140c2fd77
commit
d54a620ebd
113 files changed
+5049
-666
No files matched your search
@@ -13,12 +13,21 @@ export class AssetPanel extends Panel {
|
||||
private readonly list = new ListView();
|
||||
private readonly scroll = new ScrollContainer();
|
||||
|
||||
constructor(private readonly state: EditorState, private readonly placementPoint: () => Vec2) {
|
||||
constructor(
|
||||
private readonly state: EditorState,
|
||||
private readonly placementPoint: () => Vec2,
|
||||
) {
|
||||
super({ title: 'Assets', padding: 0, gap: 0 });
|
||||
this.minWidth = 160;
|
||||
|
||||
const add = new Button('+', { variant: 'ghost', width: 28 });
|
||||
add.onClick = () => new Menu(ASSET_TYPES.map((type) => ({ label: ASSET_TYPE_LABELS[type], onSelect: () => this.createAsset(type) }))).openBelow(add);
|
||||
add.onClick = () =>
|
||||
new Menu(
|
||||
ASSET_TYPES.map((type) => ({
|
||||
label: ASSET_TYPE_LABELS[type],
|
||||
onSelect: () => this.createAsset(type),
|
||||
})),
|
||||
).openBelow(add);
|
||||
this.header.add(add);
|
||||
|
||||
this.list.emptyText = 'No assets – press +';
|
||||
@@ -81,9 +90,17 @@ export class AssetPanel extends Panel {
|
||||
if (!asset) return;
|
||||
const items: MenuItem[] = [];
|
||||
if (asset.type === 'scene') {
|
||||
items.push({ label: 'Set as active scene', onSelect: () => this.state.execute(new SetActiveSceneCommand(id)), disabled: this.state.project.activeSceneId === id });
|
||||
items.push({
|
||||
label: 'Set as active scene',
|
||||
onSelect: () => this.state.execute(new SetActiveSceneCommand(id)),
|
||||
disabled: this.state.project.activeSceneId === id,
|
||||
});
|
||||
} else {
|
||||
items.push({ label: 'Place in scene', onSelect: () => this.place(id), disabled: !this.state.activeScene });
|
||||
items.push({
|
||||
label: 'Place in scene',
|
||||
onSelect: () => this.place(id),
|
||||
disabled: !this.state.activeScene,
|
||||
});
|
||||
}
|
||||
items.push({ label: 'Delete…', danger: true, onSelect: () => confirmDeleteAsset(this.state, id) });
|
||||
new Menu(items).open(x, y);
|
||||
|
||||
@@ -49,7 +49,10 @@ export class HierarchyPanel extends Panel {
|
||||
|
||||
state.on('project', () => this.refresh());
|
||||
// A different project brings different objects – start folded again.
|
||||
state.on('load', () => { this.expanded.clear(); this.refresh(); });
|
||||
state.on('load', () => {
|
||||
this.expanded.clear();
|
||||
this.refresh();
|
||||
});
|
||||
state.on('selection', () => (this.revealSelection() ? this.refresh() : this.syncSelection()));
|
||||
this.refresh();
|
||||
}
|
||||
@@ -88,13 +91,31 @@ export class HierarchyPanel extends Panel {
|
||||
const selected = state.selectedObjectIds.includes(id) ? state.selectedObjectIds : [id];
|
||||
const many = selected.length > 1;
|
||||
new Menu([
|
||||
{ label: 'Select asset', onSelect: () => object && state.select({ kind: 'asset', assetId: object.assetId }) },
|
||||
{ label: 'Add collider', disabled: !type || type === 'collider' || many, onSelect: () => addCollider(state, scene.id, id) },
|
||||
{ label: many ? `Create container from ${selected.length} objects` : 'Create container', onSelect: () => createContainer(state, scene.id, selected) },
|
||||
{ label: 'Unparent (move to top level)', disabled: !object?.parentId || many, onSelect: () => state.execute(new SetParentCommand(scene.id, id, '')) },
|
||||
{
|
||||
label: 'Select asset',
|
||||
onSelect: () => object && state.select({ kind: 'asset', assetId: object.assetId }),
|
||||
},
|
||||
{
|
||||
label: 'Add collider',
|
||||
disabled: !type || type === 'collider' || many,
|
||||
onSelect: () => addCollider(state, scene.id, id),
|
||||
},
|
||||
{
|
||||
label: many ? `Create container from ${selected.length} objects` : 'Create container',
|
||||
onSelect: () => createContainer(state, scene.id, selected),
|
||||
},
|
||||
{
|
||||
label: 'Unparent (move to top level)',
|
||||
disabled: !object?.parentId || many,
|
||||
onSelect: () => state.execute(new SetParentCommand(scene.id, id, '')),
|
||||
},
|
||||
{ label: 'Expand all', onSelect: () => this.expandAll() },
|
||||
{ label: 'Collapse all', onSelect: () => this.collapseAll() },
|
||||
{ label: many ? `Remove ${selected.length} objects from scene` : 'Remove from scene', danger: true, onSelect: () => removeObjects(state, scene.id, selected) },
|
||||
{
|
||||
label: many ? `Remove ${selected.length} objects from scene` : 'Remove from scene',
|
||||
danger: true,
|
||||
onSelect: () => removeObjects(state, scene.id, selected),
|
||||
},
|
||||
]).open(x, y);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,10 @@ export class Toolbar extends HStack {
|
||||
private readonly notice: Label;
|
||||
private readonly fps: Label;
|
||||
|
||||
constructor(private readonly state: EditorState, actions: ToolbarActions) {
|
||||
constructor(
|
||||
private readonly state: EditorState,
|
||||
actions: ToolbarActions,
|
||||
) {
|
||||
super({ gap: Theme.gap, padding: Theme.padding, align: 'center' });
|
||||
this.minHeight = Theme.controlHeight + Theme.padding * 2;
|
||||
this.addChildAt(this.bg, 0);
|
||||
@@ -53,7 +56,10 @@ export class Toolbar extends HStack {
|
||||
this.add(new Spacer(Theme.padding));
|
||||
|
||||
this.add(new Label('Scene', { style: 'muted' }));
|
||||
this.sceneButton = new Button('—', { width: 160, onClick: () => actions.openSceneMenu(this.sceneButton) });
|
||||
this.sceneButton = new Button('—', {
|
||||
width: 160,
|
||||
onClick: () => actions.openSceneMenu(this.sceneButton),
|
||||
});
|
||||
this.add(this.sceneButton);
|
||||
this.add(new Spacer(Theme.padding));
|
||||
|
||||
@@ -63,7 +69,11 @@ export class Toolbar extends HStack {
|
||||
this.add(this.redoButton);
|
||||
|
||||
this.add(new Spacer(), 1);
|
||||
this.playButton = new Button('▶ Play', { variant: 'primary', width: 110, onClick: () => actions.togglePlay() });
|
||||
this.playButton = new Button('▶ Play', {
|
||||
variant: 'primary',
|
||||
width: 110,
|
||||
onClick: () => actions.togglePlay(),
|
||||
});
|
||||
this.add(this.playButton);
|
||||
this.add(new Spacer(), 1);
|
||||
|
||||
|
||||
Reference in new issue
Block a user