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
@@ -1,6 +1,6 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { HttpProjectStore } from '../../src/core/store/HttpProjectStore';
|
||||
import { HttpTextureStore } from '../../src/core/store/HttpTextureStore';
|
||||
import { HttpFileStore } from '../../src/core/store/HttpFileStore';
|
||||
import { StaleRevisionError } from '../../src/core/store/ProjectStore';
|
||||
import { makeProject } from '../helpers';
|
||||
|
||||
@@ -46,11 +46,11 @@ describe('HttpProjectStore', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('HttpTextureStore', () => {
|
||||
describe('HttpFileStore', () => {
|
||||
it('uploads the raw bytes and returns what the server stored', async () => {
|
||||
const mock = stubFetch(JSON.stringify({ file: FILE, width: 2, height: 3, bytes: 4 }), { status: 201 });
|
||||
const bytes = new Blob([new Uint8Array([1, 2, 3, 4])]);
|
||||
const stored = await new HttpTextureStore('/api').upload(bytes, 'image/png');
|
||||
const stored = await new HttpFileStore('/api', 'textures').upload(bytes, 'image/png');
|
||||
|
||||
expect(stored).toEqual({ file: FILE, width: 2, height: 3, bytes: 4 });
|
||||
const [url, init] = mock.mock.calls[0]!;
|
||||
@@ -66,19 +66,19 @@ describe('HttpTextureStore', () => {
|
||||
const bytes = new Blob([new Uint8Array(4)]);
|
||||
|
||||
let mock = stubFetch(answer, { status: 201 });
|
||||
await new HttpTextureStore('/api').upload(bytes, 'image/png', 'hero idle.png');
|
||||
await new HttpFileStore('/api', 'textures').upload(bytes, 'image/png', 'hero idle.png');
|
||||
// The body stays the raw image, so the name travels in the query – encoded, not as a path.
|
||||
expect(mock.mock.calls[0]![0]).toBe('/api?r=textures&name=hero%20idle.png');
|
||||
|
||||
mock = stubFetch(answer, { status: 201 });
|
||||
await new HttpTextureStore('/api').upload(bytes, 'image/png');
|
||||
await new HttpFileStore('/api', 'textures').upload(bytes, 'image/png');
|
||||
expect(mock.mock.calls[0]![0]).toBe('/api?r=textures');
|
||||
});
|
||||
|
||||
it('reports the nginx size limit as a size problem', async () => {
|
||||
stubFetch(NGINX_413, { status: 413, statusText: 'Request Entity Too Large' });
|
||||
const bytes = new Blob([new Uint8Array(8)]);
|
||||
await expect(new HttpTextureStore('/api').upload(bytes, 'image/png')).rejects.toThrow(
|
||||
await expect(new HttpFileStore('/api', 'textures').upload(bytes, 'image/png')).rejects.toThrow(
|
||||
/too large \(413\)/i,
|
||||
);
|
||||
});
|
||||
@@ -87,12 +87,12 @@ describe('HttpTextureStore', () => {
|
||||
stubFetch(JSON.stringify([{ file: FILE, width: 8, height: 4, bytes: 12, updatedAt: 'now' }]), {
|
||||
status: 200,
|
||||
});
|
||||
await expect(new HttpTextureStore('/api').list()).resolves.toEqual([
|
||||
await expect(new HttpFileStore('/api', 'textures').list()).resolves.toEqual([
|
||||
{ file: FILE, width: 8, height: 4, bytes: 12, updatedAt: 'now' },
|
||||
]);
|
||||
|
||||
const mock = stubFetch(null, { status: 204 });
|
||||
await new HttpTextureStore('/api').delete(FILE);
|
||||
await new HttpFileStore('/api', 'textures').delete(FILE);
|
||||
expect(mock.mock.calls[0]![0]).toBe(`/api?r=${encodeURIComponent(`textures/${FILE}`)}`);
|
||||
expect(mock.mock.calls[0]![1]?.method).toBe('DELETE');
|
||||
});
|
||||
|
||||
@@ -8,13 +8,15 @@ import { CreateAssetCommand, SetAssetPropertyCommand } from '../../src/core/comm
|
||||
import { AddNodeCommand, ConnectCommand, MoveNodeCommand } from '../../src/core/commands/graphCommands';
|
||||
import { isPlaceableType } from '../../src/core/model/AssetTypes';
|
||||
import { findAssetOf } from '../../src/core/model/Project';
|
||||
import { graphFingerprint } from '../../src/core/graph/Graph';
|
||||
import { compileParticles, emptyParticleOut, type ParticleOut } from '../../src/core/particles/compile';
|
||||
import {
|
||||
compileParticles,
|
||||
emptyParticleOut,
|
||||
type ParticleOut,
|
||||
particlesFingerprint,
|
||||
} from '../../src/core/particles/compile';
|
||||
import { birthTime, fieldTime, liveCount, liveRange, prewarmOffset } from '../../src/core/particles/emission';
|
||||
birthTime,
|
||||
fieldTime,
|
||||
type LiveRange,
|
||||
liveRange,
|
||||
prewarmOffset,
|
||||
} from '../../src/core/particles/emission';
|
||||
import {
|
||||
hashId,
|
||||
mixColor,
|
||||
@@ -222,11 +224,11 @@ describe('a particle asset', () => {
|
||||
const output = p.add(PARTICLE_OUTPUT);
|
||||
const n = p.add('ParticleNumber', { value: 2 });
|
||||
p.link(n, 'value', output, 'x');
|
||||
const before = particlesFingerprint(p.asset().graph);
|
||||
const before = graphFingerprint(p.asset().graph);
|
||||
f.stack.execute(new MoveNodeCommand(p.id, n, 120, 80));
|
||||
expect(particlesFingerprint(p.asset().graph)).toBe(before);
|
||||
expect(graphFingerprint(p.asset().graph)).toBe(before);
|
||||
f.stack.execute(new AddNodeCommand(p.id, 'ParticleNumber', 0, 0, { value: 5 }));
|
||||
expect(particlesFingerprint(p.asset().graph)).not.toBe(before);
|
||||
expect(graphFingerprint(p.asset().graph)).not.toBe(before);
|
||||
});
|
||||
|
||||
it('survives a round trip through the project file, graph and rows intact', () => {
|
||||
@@ -270,6 +272,8 @@ describe('a particle asset', () => {
|
||||
});
|
||||
});
|
||||
|
||||
const liveCount = (range: LiveRange): number => Math.max(0, range.to - range.from + 1);
|
||||
|
||||
describe('when particles are alive', () => {
|
||||
const settings = {
|
||||
mode: 'continuous' as const,
|
||||
|
||||
@@ -34,7 +34,8 @@ import {
|
||||
shaderParamDefs,
|
||||
} from '../../src/core/properties/shaders';
|
||||
import { inlineInputs } from '../../src/core/graph/NodeRegistry';
|
||||
import { compileShader, shaderFingerprint } from '../../src/core/shader/compile';
|
||||
import { graphFingerprint } from '../../src/core/graph/Graph';
|
||||
import { compileShader } from '../../src/core/shader/compile';
|
||||
import {
|
||||
DEFAULT_EDGE,
|
||||
EDGE_PARAM,
|
||||
@@ -720,16 +721,16 @@ describe('the fingerprint the shader library caches on', () => {
|
||||
const f = makeProject();
|
||||
const s = shaderEditor(f);
|
||||
const n = buildAdjustColors(s);
|
||||
const before = shaderFingerprint(s.asset().graph);
|
||||
const before = graphFingerprint(s.asset().graph);
|
||||
f.stack.execute(new MoveNodeCommand(s.id, n.bright, 300, 200));
|
||||
f.stack.execute(new CollapseNodesCommand(s.id, [n.bright, n.luma], true));
|
||||
f.stack.execute(new FoldEdgeCommand(s.id, s.asset().graph.edges[0]!.id, true));
|
||||
expect(s.asset().graph.nodes.find((x) => x.id === n.bright)).toMatchObject({ x: 300, collapsed: true });
|
||||
expect(shaderFingerprint(s.asset().graph)).toBe(before);
|
||||
expect(graphFingerprint(s.asset().graph)).toBe(before);
|
||||
f.stack.execute(new SetNodeParamCommand(s.id, n.shifted, 'b', 0.25));
|
||||
const withParam = shaderFingerprint(s.asset().graph);
|
||||
const withParam = graphFingerprint(s.asset().graph);
|
||||
expect(withParam).not.toBe(before);
|
||||
s.link(n.brightness, 'value', n.output, 'alpha');
|
||||
expect(shaderFingerprint(s.asset().graph)).not.toBe(withParam);
|
||||
expect(graphFingerprint(s.asset().graph)).not.toBe(withParam);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user