feat(layers): add layer management panel

This commit is contained in:
syntaxbullet
2026-07-03 17:26:50 +02:00
parent ba3f253ee5
commit fedeaffa57
27 changed files with 1525 additions and 156 deletions

View File

@@ -0,0 +1,89 @@
import { describe, expect, test } from "bun:test";
import { commandIds } from "@commands/ids";
import type { ImageDocument } from "@core/document";
import type { Layer } from "@core/layer";
import { handleDeleteSelectionKey, resolveLayerDrop } from "./layers-panel";
const document: ImageDocument = {
id: "doc",
name: "Doc",
version: 1,
assets: [],
artboards: [
{
id: "a1",
name: "Artboard",
bounds: { x: 0, y: 0, w: 100, h: 100 },
backgroundColor: "transparent",
visible: true,
locked: false,
layers: [group("a"), group("b"), { ...group("g"), children: [group("c")] }],
},
],
};
describe("layers panel input", () => {
test("resolves dropping a layer before another layer", () => {
expect(resolveLayerDrop({ document, sourceLayerId: "b", target: { artboardId: "a1", layer: group("a") }, verticalRatio: 0.1 })).toEqual({
layerId: "b",
toArtboardId: "a1",
toParentGroupId: undefined,
toIndex: 0,
});
});
test("resolves dropping a layer into a group", () => {
expect(resolveLayerDrop({ document, sourceLayerId: "a", target: { artboardId: "a1", layer: { ...group("g"), children: [group("c")] } }, verticalRatio: 0.5 })).toEqual({
layerId: "a",
toArtboardId: "a1",
toParentGroupId: "g",
toIndex: 1,
});
});
test("dispatches delete commands for selected layers", () => {
const dispatched: unknown[] = [];
const consumed = handleDeleteSelectionKey({
event: { key: "Backspace", code: "Backspace", altKey: false, ctrlKey: false, metaKey: false, shiftKey: false },
selection: { artboardId: "a1", layerIds: ["a", "b"] },
dispatch: (commandId, payload) => {
dispatched.push({ commandId, payload });
return undefined as never;
},
});
expect(consumed).toBe(true);
expect(dispatched).toEqual([
{ commandId: commandIds.documentRemoveLayer, payload: { layerId: "a" } },
{ commandId: commandIds.documentRemoveLayer, payload: { layerId: "b" } },
]);
});
test("dispatches delete command for selected artboard when no layers are selected", () => {
const dispatched: unknown[] = [];
const consumed = handleDeleteSelectionKey({
event: { key: "Delete", code: "Delete", altKey: false, ctrlKey: false, metaKey: false, shiftKey: false },
selection: { artboardId: "a1", layerIds: [] },
dispatch: (commandId, payload) => {
dispatched.push({ commandId, payload });
return undefined as never;
},
});
expect(consumed).toBe(true);
expect(dispatched).toEqual([{ commandId: commandIds.documentRemoveArtboard, payload: { id: "a1" } }]);
});
});
function group(id: string): Extract<Layer, { type: "group" }> {
return {
id,
type: "group",
name: id,
visible: true,
locked: false,
opacity: 1,
transform: { position: { x: 0, y: 0 }, scale: { x: 1, y: 1 }, rotation: 0 },
children: [],
};
}