feat(commands): add image asset layer commands

This commit is contained in:
syntaxbullet
2026-07-03 16:18:40 +02:00
parent 97c3d3b7ae
commit 75466d2aa6
5 changed files with 110 additions and 5 deletions

View File

@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test"; import { describe, expect, test } from "bun:test";
import { createInitialAppState } from "@editor/initial-state"; import { createInitialAppState } from "@editor/initial-state";
import { documentAddArtboardCommand, documentSetArtboardBoundsCommand } from "./document"; import { documentAddArtboardCommand, documentAddAssetCommand, documentAddImageLayerCommand, documentSetArtboardBoundsCommand } from "./document";
describe("document commands", () => { describe("document commands", () => {
test("adds transparent artboard", () => { test("adds transparent artboard", () => {
@@ -20,6 +20,43 @@ describe("document commands", () => {
]); ]);
}); });
test("adds assets", () => {
const next = documentAddAssetCommand.execute(
{ state: createInitialAppState("Test") },
{ asset: { id: "asset-1", name: "Image", mimeType: "image/png", source: "blob:test", intrinsicSize: { w: 100, h: 50 } } },
);
expect(next.document.assets).toEqual([
{ id: "asset-1", name: "Image", mimeType: "image/png", source: "blob:test", intrinsicSize: { w: 100, h: 50 } },
]);
});
test("adds image layers to artboards", () => {
const state = documentAddArtboardCommand.execute(
{ state: createInitialAppState("Test") },
{ id: "a1", name: "Artboard 1", bounds: { x: 0, y: 0, w: 320, h: 240 } },
);
const next = documentAddImageLayerCommand.execute(
{ state },
{
artboardId: "a1",
layer: {
id: "l1",
type: "image",
name: "Image",
visible: true,
locked: false,
opacity: 1,
assetId: "asset-1",
transform: { position: { x: 10, y: 20 }, scale: { x: 1, y: 1 }, rotation: 0 },
},
},
);
expect(next.document.artboards[0]?.layers.map((layer) => layer.id)).toEqual(["l1"]);
});
test("sets artboard bounds", () => { test("sets artboard bounds", () => {
const state = documentAddArtboardCommand.execute( const state = documentAddArtboardCommand.execute(
{ state: createInitialAppState("Test") }, { state: createInitialAppState("Test") },

View File

@@ -1,3 +1,5 @@
import type { Asset } from "@core/asset";
import type { ImageLayer } from "@core/image-layer";
import type { Rect } from "@core/geometry"; import type { Rect } from "@core/geometry";
import type { ArtboardId } from "@core/id"; import type { ArtboardId } from "@core/id";
import type { Command } from "./command"; import type { Command } from "./command";
@@ -14,6 +16,15 @@ export type DocumentSetArtboardBoundsPayload = {
bounds: Rect; bounds: Rect;
}; };
export type DocumentAddAssetPayload = {
asset: Asset;
};
export type DocumentAddImageLayerPayload = {
artboardId: ArtboardId;
layer: ImageLayer;
};
export const documentAddArtboardCommand: Command<DocumentAddArtboardPayload> = { export const documentAddArtboardCommand: Command<DocumentAddArtboardPayload> = {
id: commandIds.documentAddArtboard, id: commandIds.documentAddArtboard,
name: "Add artboard", name: "Add artboard",
@@ -53,4 +64,41 @@ export const documentSetArtboardBoundsCommand: Command<DocumentSetArtboardBounds
}, },
}; };
export const documentCommands = [documentAddArtboardCommand, documentSetArtboardBoundsCommand] satisfies Command<unknown>[]; export const documentAddAssetCommand: Command<DocumentAddAssetPayload> = {
id: commandIds.documentAddAsset,
name: "Add asset",
execute({ state }, payload) {
if (state.document.assets.some((asset) => asset.id === payload.asset.id)) return state;
return {
...state,
document: {
...state.document,
assets: [...state.document.assets, payload.asset],
},
};
},
};
export const documentAddImageLayerCommand: Command<DocumentAddImageLayerPayload> = {
id: commandIds.documentAddImageLayer,
name: "Add image layer",
execute({ state }, payload) {
return {
...state,
document: {
...state.document,
artboards: state.document.artboards.map((artboard) =>
artboard.id === payload.artboardId ? { ...artboard, layers: [...artboard.layers, payload.layer] } : artboard,
),
},
};
},
};
export const documentCommands = [
documentAddArtboardCommand,
documentSetArtboardBoundsCommand,
documentAddAssetCommand,
documentAddImageLayerCommand,
] satisfies Command<unknown>[];

View File

@@ -1,6 +1,8 @@
export const commandIds = { export const commandIds = {
documentAddArtboard: "document.addArtboard", documentAddArtboard: "document.addArtboard",
documentSetArtboardBounds: "document.setArtboardBounds", documentSetArtboardBounds: "document.setArtboardBounds",
documentAddAsset: "document.addAsset",
documentAddImageLayer: "document.addImageLayer",
selectionSet: "selection.set", selectionSet: "selection.set",
selectionClear: "selection.clear", selectionClear: "selection.clear",
selectionAddLayer: "selection.addLayer", selectionAddLayer: "selection.addLayer",

View File

@@ -1,6 +1,17 @@
export type { Command, CommandContext } from "./command"; export type { Command, CommandContext } from "./command";
export { documentAddArtboardCommand, documentCommands } from "./document"; export {
export type { DocumentAddArtboardPayload } from "./document"; documentAddArtboardCommand,
documentAddAssetCommand,
documentAddImageLayerCommand,
documentCommands,
documentSetArtboardBoundsCommand,
} from "./document";
export type {
DocumentAddArtboardPayload,
DocumentAddAssetPayload,
DocumentAddImageLayerPayload,
DocumentSetArtboardBoundsPayload,
} from "./document";
export type { CommandDispatcher, Dispatch } from "./dispatcher"; export type { CommandDispatcher, Dispatch } from "./dispatcher";
export type { CommandId, CommandPayloads } from "./payloads"; export type { CommandId, CommandPayloads } from "./payloads";
export { createCommandDispatcher } from "./dispatcher"; export { createCommandDispatcher } from "./dispatcher";

View File

@@ -1,5 +1,10 @@
import { commandIds } from "./ids"; import { commandIds } from "./ids";
import type { DocumentAddArtboardPayload, DocumentSetArtboardBoundsPayload } from "./document"; import type {
DocumentAddArtboardPayload,
DocumentAddAssetPayload,
DocumentAddImageLayerPayload,
DocumentSetArtboardBoundsPayload,
} from "./document";
import type { SelectionAddLayerPayload, SelectionSetPayload } from "./selection"; import type { SelectionAddLayerPayload, SelectionSetPayload } from "./selection";
import type { ToolSetActivePayload } from "./tool"; import type { ToolSetActivePayload } from "./tool";
import type { TransformBeginPayload, TransformUpdatePayload } from "./transform"; import type { TransformBeginPayload, TransformUpdatePayload } from "./transform";
@@ -14,6 +19,8 @@ import type {
export type CommandPayloads = { export type CommandPayloads = {
[commandIds.documentAddArtboard]: DocumentAddArtboardPayload; [commandIds.documentAddArtboard]: DocumentAddArtboardPayload;
[commandIds.documentSetArtboardBounds]: DocumentSetArtboardBoundsPayload; [commandIds.documentSetArtboardBounds]: DocumentSetArtboardBoundsPayload;
[commandIds.documentAddAsset]: DocumentAddAssetPayload;
[commandIds.documentAddImageLayer]: DocumentAddImageLayerPayload;
[commandIds.selectionSet]: SelectionSetPayload; [commandIds.selectionSet]: SelectionSetPayload;
[commandIds.selectionClear]: void; [commandIds.selectionClear]: void;
[commandIds.selectionAddLayer]: SelectionAddLayerPayload; [commandIds.selectionAddLayer]: SelectionAddLayerPayload;