feat(commands): add selection commands

This commit is contained in:
syntaxbullet
2026-07-03 09:51:26 +02:00
parent d77bdc22a9
commit ca9e265614
5 changed files with 96 additions and 1 deletions

View File

@@ -1,4 +1,5 @@
import { createCommandRegistry } from "@commands/registry"; import { createCommandRegistry } from "@commands/registry";
import { selectionCommands } from "@commands/selection";
import { viewportCommands } from "@commands/viewport"; import { viewportCommands } from "@commands/viewport";
import { createInitialAppState } from "@editor/initial-state"; import { createInitialAppState } from "@editor/initial-state";
import { createAppStore } from "@editor/store"; import { createAppStore } from "@editor/store";
@@ -6,7 +7,7 @@ import { createAppStore } from "@editor/store";
export type ImageStudioApp = ReturnType<typeof createImageStudioApp>; export type ImageStudioApp = ReturnType<typeof createImageStudioApp>;
export function createImageStudioApp(options?: { documentName?: string }) { export function createImageStudioApp(options?: { documentName?: string }) {
const registry = createCommandRegistry([...viewportCommands]); const registry = createCommandRegistry([...viewportCommands, ...selectionCommands]);
const store = createAppStore(createInitialAppState(options?.documentName), registry); const store = createAppStore(createInitialAppState(options?.documentName), registry);
return { return {

View File

@@ -4,6 +4,8 @@ export type { CommandId, CommandPayloads } from "./payloads";
export { createCommandDispatcher } from "./dispatcher"; export { createCommandDispatcher } from "./dispatcher";
export type { CommandRegistry } from "./registry"; export type { CommandRegistry } from "./registry";
export { createCommandRegistry } from "./registry"; export { createCommandRegistry } from "./registry";
export { selectionAddLayerCommand, selectionClearCommand, selectionCommands, selectionSetCommand } from "./selection";
export type { SelectionAddLayerPayload, SelectionSetPayload } from "./selection";
export { export {
viewportCommands, viewportCommands,
viewportPanCommand, viewportPanCommand,

View File

@@ -1,3 +1,4 @@
import type { SelectionAddLayerPayload, SelectionSetPayload } from "./selection";
import type { import type {
ViewportPanPayload, ViewportPanPayload,
ViewportSetSizePayload, ViewportSetSizePayload,
@@ -6,6 +7,9 @@ import type {
} from "./viewport"; } from "./viewport";
export type CommandPayloads = { export type CommandPayloads = {
"selection.set": SelectionSetPayload;
"selection.clear": void;
"selection.addLayer": SelectionAddLayerPayload;
"viewport.pan": ViewportPanPayload; "viewport.pan": ViewportPanPayload;
"viewport.setZoom": ViewportSetZoomPayload; "viewport.setZoom": ViewportSetZoomPayload;
"viewport.zoomAroundPoint": ViewportZoomAroundPointPayload; "viewport.zoomAroundPoint": ViewportZoomAroundPointPayload;

View File

@@ -0,0 +1,25 @@
import { describe, expect, test } from "bun:test";
import { createInitialAppState } from "@editor/initial-state";
import { selectionAddLayerCommand, selectionClearCommand, selectionSetCommand } from "./selection";
describe("selection commands", () => {
test("sets selection", () => {
const next = selectionSetCommand.execute({ state: createInitialAppState("Test") }, { artboardId: "a1", layerIds: ["l1"] });
expect(next.editor.selection).toEqual({ artboardId: "a1", layerIds: ["l1"] });
});
test("adds unique layer selection", () => {
const selected = selectionSetCommand.execute({ state: createInitialAppState("Test") }, { layerIds: ["l1"] });
const next = selectionAddLayerCommand.execute({ state: selected }, { layerId: "l2" });
const same = selectionAddLayerCommand.execute({ state: next }, { layerId: "l2" });
expect(next.editor.selection.layerIds).toEqual(["l1", "l2"]);
expect(same).toBe(next);
});
test("clears selection", () => {
const selected = selectionSetCommand.execute({ state: createInitialAppState("Test") }, { artboardId: "a1", layerIds: ["l1"] });
const next = selectionClearCommand.execute({ state: selected }, undefined);
expect(next.editor.selection).toEqual({ layerIds: [] });
});
});

63
commands/selection.ts Normal file
View File

@@ -0,0 +1,63 @@
import type { ArtboardId, LayerId } from "@core/id";
import type { Command } from "./command";
export type SelectionSetPayload = {
artboardId?: ArtboardId;
layerIds: LayerId[];
};
export type SelectionAddLayerPayload = {
layerId: LayerId;
};
export const selectionSetCommand: Command<SelectionSetPayload> = {
id: "selection.set",
name: "Set selection",
execute({ state }, payload) {
return {
...state,
editor: {
...state.editor,
selection: {
artboardId: payload.artboardId,
layerIds: [...payload.layerIds],
},
},
};
},
};
export const selectionClearCommand: Command = {
id: "selection.clear",
name: "Clear selection",
execute({ state }) {
return {
...state,
editor: {
...state.editor,
selection: { layerIds: [] },
},
};
},
};
export const selectionAddLayerCommand: Command<SelectionAddLayerPayload> = {
id: "selection.addLayer",
name: "Add layer to selection",
execute({ state }, payload) {
if (state.editor.selection.layerIds.includes(payload.layerId)) return state;
return {
...state,
editor: {
...state.editor,
selection: {
...state.editor.selection,
layerIds: [...state.editor.selection.layerIds, payload.layerId],
},
},
};
},
};
export const selectionCommands = [selectionSetCommand, selectionClearCommand, selectionAddLayerCommand] satisfies Command<unknown>[];