Three stores, three importers and three pickers that differ only in their type are one of each (#85)
Refactoring round one: HttpFontStore/HttpSoundStore/HttpTextureStore collapse into a generic HttpFileStore over a FileStore contract; fontImport/soundImport/textureImport share assetImport with an ImportKind per sort; ImagePicker/FontPicker/SoundPicker sit on FilePicker and describe only their row. The group/comment drag logic leaves GraphCanvas for annotationDrag.ts (with its own unit test), declaredRows.ts replaces the duplicated row build in particles.ts and shaders.ts, and dead code in font/sound/compile/emission/transform/actions is gone. README: asset list, check count 958, the two-job CI, the Scenes wiki page. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
15bdfdf07a
commit
cc6db5f350
42 files changed
+1375
-1650
No files matched your search
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* A drag in the graph moves everything it carries exactly once. A group carries the nodes centred
|
||||
* in it and the boxes inside it; a band may have caught a group *and* one of its nodes – that node
|
||||
* is claimed twice and must move once, or it would end up twice as far as the rest.
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { Graph, GraphNode } from '../../src/core/graph';
|
||||
import {
|
||||
annotationBoxes,
|
||||
boxSeeds,
|
||||
placeTravellers,
|
||||
recordTravellers,
|
||||
travellers,
|
||||
} from '../../src/editor/graph/annotationDrag';
|
||||
import type { Box } from '../../src/editor/framing';
|
||||
import { makeBehavior, makeProject } from '../helpers';
|
||||
|
||||
const node = (id: string, x: number, y: number): GraphNode => ({ id, type: 'Add', x, y, params: {} });
|
||||
const NODE = { w: 100, h: 50 };
|
||||
const drawn = (graph: Graph): Box[] => graph.nodes.map((n) => ({ id: n.id, x: n.x, y: n.y, ...NODE }));
|
||||
|
||||
/** An outer group holding an inner group and a comment; `in` sits in both groups, `far` in neither. */
|
||||
function graph(): Graph {
|
||||
return {
|
||||
nodes: [node('in', 120, 120), node('edge', 20, 20), node('far', 900, 900)],
|
||||
edges: [],
|
||||
groups: [
|
||||
{ id: 'outer', x: 0, y: 0, w: 400, h: 300, text: 'Outer' },
|
||||
{ id: 'inner', x: 100, y: 100, w: 150, h: 100, text: 'Inner' },
|
||||
],
|
||||
notes: [{ id: 'note', x: 300, y: 200, w: 80, h: 60, text: 'Careful' }],
|
||||
};
|
||||
}
|
||||
|
||||
describe('what a group drag carries', () => {
|
||||
it('takes the nodes centred in it, the boxes inside it, and their nodes – each once', () => {
|
||||
const g = graph();
|
||||
const along = travellers(g, drawn(g), boxSeeds(g, ['outer']), []);
|
||||
expect(along.boxes.map((b) => b.id).sort()).toEqual(['inner', 'note', 'outer']);
|
||||
// `edge` is centred at (70, 45), inside; `in` is inside both groups but listed once; `far` is not.
|
||||
expect(along.nodes.map((n) => n.nodeId).sort()).toEqual(['edge', 'in']);
|
||||
});
|
||||
|
||||
it('claims a node once even when the band caught both it and its group', () => {
|
||||
const g = graph();
|
||||
const along = travellers(g, drawn(g), boxSeeds(g, ['outer']), ['in', 'far']);
|
||||
expect(along.nodes.map((n) => n.nodeId).sort()).toEqual(['edge', 'far', 'in']);
|
||||
placeTravellers(g, along, () => ({ x: 10, y: 0 }));
|
||||
expect(g.nodes.map((n) => n.x)).toEqual([130, 30, 910]);
|
||||
expect(g.groups?.map((b) => b.x)).toEqual([10, 110]);
|
||||
expect(g.notes?.map((b) => b.x)).toEqual([310]);
|
||||
});
|
||||
|
||||
it('carries nothing along for a comment, which frames nothing', () => {
|
||||
const g = graph();
|
||||
const along = travellers(g, drawn(g), boxSeeds(g, ['note']), []);
|
||||
expect(along.boxes.map((b) => b.id)).toEqual(['note']);
|
||||
expect(along.nodes).toEqual([]);
|
||||
});
|
||||
|
||||
it('leaves out the grabbed box when asked, so it is not seeded twice', () => {
|
||||
const g = graph();
|
||||
expect(boxSeeds(g, ['outer', 'note'], 'outer').map((b) => b.id)).toEqual(['note']);
|
||||
expect(annotationBoxes(g).map((b) => b.id)).toEqual(['outer', 'inner', 'note']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('writing a drag back', () => {
|
||||
it('records one command per traveller from the start to where the preview left it', () => {
|
||||
const f = makeProject();
|
||||
const behavior = makeBehavior(f, 'B');
|
||||
const g = behavior.graph;
|
||||
g.nodes.push(node('a', 0, 0));
|
||||
g.groups = [{ id: 'grp', x: -50, y: -50, w: 200, h: 200, text: 'G' }];
|
||||
const along = travellers(g, drawn(g), boxSeeds(g, ['grp']), []);
|
||||
placeTravellers(g, along, () => ({ x: 24, y: 48 }));
|
||||
|
||||
const commands = recordTravellers(g, behavior.id, along);
|
||||
// The preview is undone in the model, so the commands own the whole change…
|
||||
expect(g.nodes[0]).toMatchObject({ x: 0, y: 0 });
|
||||
expect(g.groups[0]).toMatchObject({ x: -50, y: -50 });
|
||||
// …and running them lands everything where the drag ended.
|
||||
for (const command of commands) command.execute(f.project);
|
||||
expect(g.nodes[0]).toMatchObject({ x: 24, y: 48 });
|
||||
expect(g.groups[0]).toMatchObject({ x: -26, y: -2, w: 200, h: 200 });
|
||||
expect(commands).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user