feat: implement document actions for managing artboards, zoom, and history; update UI components to utilize new actions
This commit is contained in:
@@ -14,6 +14,7 @@ import { projectCommands } from "@commands/project";
|
||||
import { createInitialAppState } from "@editor/initial-state";
|
||||
import { createAppStore } from "@editor/store";
|
||||
import { createGenerationWorkflow } from "@operations/generation/workflow";
|
||||
import { createDocumentActions } from "./document-actions";
|
||||
|
||||
export type ImageStudioApp = ReturnType<typeof createImageStudioApp>;
|
||||
|
||||
@@ -21,6 +22,7 @@ export function createImageStudioApp(options?: { documentName?: string; createDe
|
||||
const registry = createCommandRegistry([...projectCommands, ...viewportCommands, ...selectionCommands, ...documentCommands, ...toolCommands, ...generationCommands, ...transformCommands, ...historyCommands, ...commandPaletteCommands, ...workspaceCommands, ...editorCommands]);
|
||||
const store = createAppStore(createInitialAppState(options?.documentName), registry);
|
||||
const generation = createGenerationWorkflow(store);
|
||||
const documentActions = createDocumentActions(store, generation);
|
||||
|
||||
if (options?.createDefaultArtboard !== false) {
|
||||
const artboardId = crypto.randomUUID();
|
||||
@@ -29,12 +31,13 @@ export function createImageStudioApp(options?: { documentName?: string; createDe
|
||||
name: "Artboard 1",
|
||||
bounds: { x: -400, y: -300, w: 800, h: 600 },
|
||||
});
|
||||
store.dispatch(commandIds.viewportFitArtboard, { artboardId });
|
||||
documentActions.fitArtboard(artboardId);
|
||||
}
|
||||
|
||||
return {
|
||||
registry,
|
||||
store,
|
||||
actions: { document: documentActions },
|
||||
workflows: { generation },
|
||||
};
|
||||
}
|
||||
|
||||
34
app/document-actions.test.ts
Normal file
34
app/document-actions.test.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { commandIds } from "@commands/ids";
|
||||
import { createImageStudioApp } from "./app";
|
||||
|
||||
describe("document actions", () => {
|
||||
test("share zoom, fit, and history behavior across UI entry points", () => {
|
||||
const app = createImageStudioApp();
|
||||
const artboard = app.store.getState().document.artboards[0];
|
||||
if (!artboard) throw new Error("Expected the default artboard.");
|
||||
|
||||
app.actions.document.zoomTo(2);
|
||||
app.actions.document.zoomBy(0.5);
|
||||
expect(app.store.getState().editor.viewport.zoom).toBe(1);
|
||||
|
||||
app.actions.document.fitArtboard(artboard.id);
|
||||
expect(app.store.getState().editor.viewport.center).toEqual({ x: 0, y: 0 });
|
||||
|
||||
expect(app.actions.document.canUndo()).toBe(true);
|
||||
app.actions.document.undo();
|
||||
expect(app.store.getState().document.artboards).toHaveLength(0);
|
||||
expect(app.actions.document.canRedo()).toBe(true);
|
||||
app.actions.document.redo();
|
||||
expect(app.store.getState().document.artboards).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("opens Generate through the canonical workspace command", () => {
|
||||
const app = createImageStudioApp({ createDefaultArtboard: false });
|
||||
app.actions.document.openGenerate();
|
||||
expect(app.store.getState().editor.workspace.panel).toBe("generate");
|
||||
|
||||
app.store.dispatch(commandIds.workspaceSetPanel, { panel: "none" });
|
||||
expect(app.store.getState().editor.workspace.panel).toBe("none");
|
||||
});
|
||||
});
|
||||
58
app/document-actions.ts
Normal file
58
app/document-actions.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { commandIds } from "@commands/ids";
|
||||
import type { ArtboardId } from "@core/id";
|
||||
import type { AppStore } from "@editor/store";
|
||||
import { downloadArtboardPng } from "@operations/export/downloadArtboard";
|
||||
import type { GenerationWorkflow } from "@operations/generation/workflow";
|
||||
|
||||
export type DocumentActions = ReturnType<typeof createDocumentActions>;
|
||||
|
||||
export function createDocumentActions(store: AppStore, generation: GenerationWorkflow) {
|
||||
return {
|
||||
openGenerate() {
|
||||
store.dispatch(commandIds.workspaceSetPanel, { panel: "generate" });
|
||||
},
|
||||
|
||||
generate() {
|
||||
return generation.generate();
|
||||
},
|
||||
|
||||
exportArtboard(artboardId?: ArtboardId) {
|
||||
const state = store.getState();
|
||||
const resolvedId = artboardId ?? state.editor.selection.artboardId;
|
||||
const artboard = resolvedId
|
||||
? state.document.artboards.find((candidate) => candidate.id === resolvedId)
|
||||
: state.document.artboards[0];
|
||||
if (!artboard) return Promise.resolve();
|
||||
return downloadArtboardPng(artboard, state.document.assets);
|
||||
},
|
||||
|
||||
fitArtboard(artboardId?: ArtboardId) {
|
||||
store.dispatch(commandIds.viewportFitArtboard, artboardId ? { artboardId } : undefined);
|
||||
},
|
||||
|
||||
zoomBy(factor: number) {
|
||||
const zoom = store.getState().editor.viewport.zoom;
|
||||
store.dispatch(commandIds.viewportSetZoom, { zoom: zoom * factor });
|
||||
},
|
||||
|
||||
zoomTo(zoom: number) {
|
||||
store.dispatch(commandIds.viewportSetZoom, { zoom });
|
||||
},
|
||||
|
||||
undo() {
|
||||
store.dispatch(commandIds.historyUndo, undefined);
|
||||
},
|
||||
|
||||
canUndo() {
|
||||
return store.getState().history.past.length > 0;
|
||||
},
|
||||
|
||||
redo() {
|
||||
store.dispatch(commandIds.historyRedo, undefined);
|
||||
},
|
||||
|
||||
canRedo() {
|
||||
return store.getState().history.future.length > 0;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
export type { ImageStudioApp } from "./app";
|
||||
export { createImageStudioApp } from "./app";
|
||||
export type { DocumentActions } from "./document-actions";
|
||||
|
||||
Reference in New Issue
Block a user