From 75466d2aa65a69cc45fa9e4c12ba8379469cf4ea Mon Sep 17 00:00:00 2001 From: syntaxbullet Date: Fri, 3 Jul 2026 16:18:40 +0200 Subject: [PATCH] feat(commands): add image asset layer commands --- commands/document.test.ts | 39 +++++++++++++++++++++++++++++- commands/document.ts | 50 ++++++++++++++++++++++++++++++++++++++- commands/ids.ts | 2 ++ commands/index.ts | 15 ++++++++++-- commands/payloads.ts | 9 ++++++- 5 files changed, 110 insertions(+), 5 deletions(-) diff --git a/commands/document.test.ts b/commands/document.test.ts index b41c6e7..3f546b5 100644 --- a/commands/document.test.ts +++ b/commands/document.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test"; import { createInitialAppState } from "@editor/initial-state"; -import { documentAddArtboardCommand, documentSetArtboardBoundsCommand } from "./document"; +import { documentAddArtboardCommand, documentAddAssetCommand, documentAddImageLayerCommand, documentSetArtboardBoundsCommand } from "./document"; describe("document commands", () => { 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", () => { const state = documentAddArtboardCommand.execute( { state: createInitialAppState("Test") }, diff --git a/commands/document.ts b/commands/document.ts index 5fcee16..f4d15aa 100644 --- a/commands/document.ts +++ b/commands/document.ts @@ -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 { ArtboardId } from "@core/id"; import type { Command } from "./command"; @@ -14,6 +16,15 @@ export type DocumentSetArtboardBoundsPayload = { bounds: Rect; }; +export type DocumentAddAssetPayload = { + asset: Asset; +}; + +export type DocumentAddImageLayerPayload = { + artboardId: ArtboardId; + layer: ImageLayer; +}; + export const documentAddArtboardCommand: Command = { id: commandIds.documentAddArtboard, name: "Add artboard", @@ -53,4 +64,41 @@ export const documentSetArtboardBoundsCommand: Command[]; +export const documentAddAssetCommand: Command = { + 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 = { + 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[]; diff --git a/commands/ids.ts b/commands/ids.ts index 245eef9..ecf55b9 100644 --- a/commands/ids.ts +++ b/commands/ids.ts @@ -1,6 +1,8 @@ export const commandIds = { documentAddArtboard: "document.addArtboard", documentSetArtboardBounds: "document.setArtboardBounds", + documentAddAsset: "document.addAsset", + documentAddImageLayer: "document.addImageLayer", selectionSet: "selection.set", selectionClear: "selection.clear", selectionAddLayer: "selection.addLayer", diff --git a/commands/index.ts b/commands/index.ts index 618cf7f..92e2bcb 100644 --- a/commands/index.ts +++ b/commands/index.ts @@ -1,6 +1,17 @@ export type { Command, CommandContext } from "./command"; -export { documentAddArtboardCommand, documentCommands } from "./document"; -export type { DocumentAddArtboardPayload } from "./document"; +export { + documentAddArtboardCommand, + documentAddAssetCommand, + documentAddImageLayerCommand, + documentCommands, + documentSetArtboardBoundsCommand, +} from "./document"; +export type { + DocumentAddArtboardPayload, + DocumentAddAssetPayload, + DocumentAddImageLayerPayload, + DocumentSetArtboardBoundsPayload, +} from "./document"; export type { CommandDispatcher, Dispatch } from "./dispatcher"; export type { CommandId, CommandPayloads } from "./payloads"; export { createCommandDispatcher } from "./dispatcher"; diff --git a/commands/payloads.ts b/commands/payloads.ts index af77e57..ac6a508 100644 --- a/commands/payloads.ts +++ b/commands/payloads.ts @@ -1,5 +1,10 @@ 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 { ToolSetActivePayload } from "./tool"; import type { TransformBeginPayload, TransformUpdatePayload } from "./transform"; @@ -14,6 +19,8 @@ import type { export type CommandPayloads = { [commandIds.documentAddArtboard]: DocumentAddArtboardPayload; [commandIds.documentSetArtboardBounds]: DocumentSetArtboardBoundsPayload; + [commandIds.documentAddAsset]: DocumentAddAssetPayload; + [commandIds.documentAddImageLayer]: DocumentAddImageLayerPayload; [commandIds.selectionSet]: SelectionSetPayload; [commandIds.selectionClear]: void; [commandIds.selectionAddLayer]: SelectionAddLayerPayload;