Z-order via position.z and reset-to-default for asset properties
- Position is now { x, y, z }; higher z draws on top, ties keep hierarchy order
- Viewport sorts the objects layer deterministically (syncDrawOrder)
- Inspector shows a Z field; asset properties that differ from the schema
default are marked bold with a reset button (ResetAssetPropertyCommand)
- isModified() helper, unit tests, E2E checks and seed updated
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
aaa8154adf
commit
d4b1ffa051
18 files changed
+232
-63
No files matched your search
@@ -3,6 +3,7 @@ import {
|
||||
CreateAssetCommand,
|
||||
DeleteAssetCommand,
|
||||
RenameAssetCommand,
|
||||
ResetAssetPropertyCommand,
|
||||
SetActiveSceneCommand,
|
||||
SetAssetPropertyCommand,
|
||||
} from '../../src/core/commands/assetCommands';
|
||||
@@ -54,6 +55,19 @@ describe('asset commands', () => {
|
||||
it('rejects unknown properties', () => {
|
||||
const { stack, spriteId } = setup();
|
||||
expect(() => stack.execute(new SetAssetPropertyCommand(spriteId, 'x', 5))).toThrow(/not defined/);
|
||||
expect(() => stack.execute(new ResetAssetPropertyCommand(spriteId, 'x'))).toThrow(/not defined/);
|
||||
});
|
||||
|
||||
it('resets a property to its schema default with undo', () => {
|
||||
const { project, stack, spriteId } = setup();
|
||||
const sprite = () => project.assets.find((a) => a.id === spriteId)!;
|
||||
stack.execute(new SetAssetPropertyCommand(spriteId, 'height', 60));
|
||||
stack.execute(new ResetAssetPropertyCommand(spriteId, 'height'));
|
||||
expect(sprite().props.height).toBe(100);
|
||||
stack.undo();
|
||||
expect(sprite().props.height).toBe(60);
|
||||
stack.redo();
|
||||
expect(sprite().props.height).toBe(100);
|
||||
});
|
||||
|
||||
it('deletes an asset together with its scene objects and restores both on undo', () => {
|
||||
@@ -67,8 +81,8 @@ describe('asset commands', () => {
|
||||
stack.undo();
|
||||
expect(project.assets).toHaveLength(2);
|
||||
expect(findScene(project, sceneId)!.objects.map((o) => o.position)).toEqual([
|
||||
{ x: 1, y: 2 },
|
||||
{ x: 3, y: 4 },
|
||||
{ x: 1, y: 2, z: 0 },
|
||||
{ x: 3, y: 4, z: 0 },
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -92,7 +106,7 @@ describe('scene object commands', () => {
|
||||
const object = findScene(project, sceneId)!.objects[0]!;
|
||||
expect(object.id).toBe(place.objectId);
|
||||
expect(object.assetId).toBe(spriteId);
|
||||
expect(object.position).toEqual({ x: 5, y: 6 });
|
||||
expect(object.position).toEqual({ x: 5, y: 6, z: 0 });
|
||||
expect(object.overrides).toEqual({});
|
||||
|
||||
expect(() => stack.execute(new PlaceAssetCommand(sceneId, sceneId, { x: 0, y: 0 }))).toThrow(/cannot be placed/);
|
||||
@@ -105,8 +119,11 @@ describe('scene object commands', () => {
|
||||
stack.execute(place);
|
||||
const object = () => findScene(project, sceneId)!.objects.find((o) => o.id === place.objectId)!;
|
||||
|
||||
stack.execute(new MoveSceneObjectCommand(sceneId, place.objectId, { x: 40, y: 50 }));
|
||||
expect(object().position).toEqual({ x: 40, y: 50 });
|
||||
stack.execute(new MoveSceneObjectCommand(sceneId, place.objectId, { x: 40, y: 50, z: 3 }));
|
||||
expect(object().position).toEqual({ x: 40, y: 50, z: 3 });
|
||||
stack.undo();
|
||||
expect(object().position).toEqual({ x: 0, y: 0, z: 0 });
|
||||
stack.redo();
|
||||
|
||||
stack.execute(new SetOverrideCommand(sceneId, place.objectId, 'color', 0x123456));
|
||||
expect(object().overrides).toEqual({ color: 0x123456 });
|
||||
@@ -120,7 +137,7 @@ describe('scene object commands', () => {
|
||||
stack.execute(new RemoveSceneObjectCommand(sceneId, place.objectId));
|
||||
expect(findScene(project, sceneId)!.objects).toHaveLength(0);
|
||||
stack.undo();
|
||||
expect(object().position).toEqual({ x: 40, y: 50 });
|
||||
expect(object().position).toEqual({ x: 40, y: 50, z: 3 });
|
||||
});
|
||||
|
||||
it('clears the redo stack on a new command', () => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest';
|
||||
import { buildAsset } from '../../src/core/commands/assetCommands';
|
||||
import { createSceneObject } from '../../src/core/model/SceneObject';
|
||||
import { coerceValue, defaultsFor, schemaFor } from '../../src/core/properties/PropertySchema';
|
||||
import { inheritedValue, isOverridden, resolveAssetProps, resolveSceneObjectProps } from '../../src/core/properties/resolve';
|
||||
import { inheritedValue, isModified, isOverridden, resolveAssetProps, resolveSceneObjectProps } from '../../src/core/properties/resolve';
|
||||
|
||||
describe('PropertySchema', () => {
|
||||
it('has no position on any asset type', () => {
|
||||
@@ -49,10 +49,20 @@ describe('resolve', () => {
|
||||
expect(inheritedValue(sprite, 'color')).toBe(0x00ff00);
|
||||
});
|
||||
|
||||
it('detects asset values that differ from the schema default', () => {
|
||||
const sprite = buildAsset('sprite', 'Box');
|
||||
expect(isModified(sprite, 'width')).toBe(false); // explicit default counts as unmodified
|
||||
sprite.props.width = 250;
|
||||
expect(isModified(sprite, 'width')).toBe(true);
|
||||
delete sprite.props.height; // missing → resolved from default → unmodified
|
||||
expect(isModified(sprite, 'height')).toBe(false);
|
||||
expect(isModified(sprite, 'bogus')).toBe(false);
|
||||
});
|
||||
|
||||
it('keeps position on the scene object only', () => {
|
||||
const sprite = buildAsset('sprite', 'Box');
|
||||
const object = createSceneObject('o1', sprite.id, { x: 10, y: 20 });
|
||||
expect(object.position).toEqual({ x: 10, y: 20 });
|
||||
expect(object.position).toEqual({ x: 10, y: 20, z: 0 });
|
||||
expect('position' in sprite).toBe(false);
|
||||
expect(resolveSceneObjectProps(sprite, object)).not.toHaveProperty('x');
|
||||
});
|
||||
|
||||
@@ -50,7 +50,7 @@ describe('serialize', () => {
|
||||
id: 'o1',
|
||||
assetId: 'sp1',
|
||||
name: '',
|
||||
position: { x: 5, y: 0 },
|
||||
position: { x: 5, y: 0, z: 0 },
|
||||
overrides: { color: 0x00ff00, width: 1 },
|
||||
});
|
||||
expect(project.assets[1]!.name).toBe('sprite');
|
||||
|
||||
Reference in new issue
Block a user