@calca/canvas-flow 0.0.0-stage → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,39 @@
1
- # Temporary Holding Version
1
+ # @calca/canvas-flow
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ React Flow adapter for the Calca canvas — the open canvas layer that renders a versioned document model as interactive, agent-editable frames. Turns a [`@calca/canvas-base`](https://www.npmjs.com/package/@calca/canvas-base) `CanvasStore` into a live [React Flow](https://reactflow.dev) projection, and maps user edits back into canvas commands with undo history.
4
+
5
+ Use it to build an infinite-canvas UI — design canvas, prototype board, AI generation surface, marketing layout tool — without owning the document model, ordering, undo, or change-projection logic yourself.
6
+
7
+ ## What it gives you
8
+
9
+ - **`<CanvasProvider>` + `<CanvasArea>`** — a ready canvas surface: pan/zoom viewport, selection, drag, frame rendering.
10
+ - **`useCanvas()` / `useCanvasViewport()`** — imperative `CanvasHandle` (`fitToView`, viewport reads) inside the provider.
11
+ - **Store → React Flow projection** — `CanvasProjection`, `ShapeView`, `BindingView` adapt records and bindings to nodes/edges; register renderers per shape type via `ShapeView`.
12
+ - **Commands in, events out** — user edits become `canvas-base` commands (with origin + history), and store `change` events re-project the view.
13
+ - **Jotai atoms** — `canvasOffsetAtom`, `canvasScaleAtom`, `isPanningAtom`, `groupsAtom` for composing your own HUD/toolbars.
14
+ - **`@xyflow/*` confined here** — your app and the `@calca/*` base packages never depend on React Flow; swap renderers without touching the document layer.
15
+
16
+ ```tsx
17
+ import { createCanvasStore } from "@calca/canvas-base";
18
+ import { CanvasProvider, CanvasArea } from "@calca/canvas-flow";
19
+
20
+ const store = createCanvasStore();
21
+
22
+ export function Board() {
23
+ return (
24
+ <CanvasProvider store={store}>
25
+ <CanvasArea />
26
+ </CanvasProvider>
27
+ );
28
+ }
29
+ ```
30
+
31
+ ## Stack
32
+
33
+ TypeScript · React 19 · React Flow 12 (`@xyflow/react`) · Jotai. Pairs with `@calca/canvas-base` (document model), `@calca/canvas-ui` (headless chrome), `@calca/mcp` (drive the same canvas from an AI agent over MCP).
34
+
35
+ Part of [Calca](https://calca.illo.fyi) — open source, BYOK AI canvas for the web and desktop.
36
+
37
+ ## License
38
+
39
+ Apache-2.0
@@ -0,0 +1,243 @@
1
+ import { Edge, Node, NodeChange, ReactFlowProps } from "@xyflow/react";
2
+ import "@xyflow/react/dist/style.css";
3
+ import { ReactNode } from "react";
4
+ import { BindingRecord, CanvasStore, Command, ShapeRecord } from "@calca/canvas-base";
5
+ import { CanvasImage, Comment, DesignIteration, GenerationGroup, PipelineStatus, Point, ToolMode } from "@calca/shared";
6
+ //#region src/hooks/use-canvas.d.ts
7
+ interface Point$1 {
8
+ x: number;
9
+ y: number;
10
+ }
11
+ type CanvasHandle = {
12
+ offset: Point$1;
13
+ scale: number;
14
+ resetView: () => void;
15
+ zoomIn: () => void;
16
+ zoomOut: () => void;
17
+ fitToView: () => void;
18
+ zoomToFit: (bounds: {
19
+ minX: number;
20
+ minY: number;
21
+ maxX: number;
22
+ maxY: number;
23
+ }) => void;
24
+ screenToCanvas: (screenX: number, screenY: number, rect: DOMRect) => Point$1;
25
+ };
26
+ export declare function useCanvasViewport(): CanvasHandle;
27
+ /** Backward-compatible alias for code that still imports `useCanvas`. */
28
+ export declare const useCanvas: typeof useCanvasViewport;
29
+ //#endregion
30
+ //#region src/components/CanvasArea.d.ts
31
+ type RubberBand = {
32
+ startX: number;
33
+ startY: number;
34
+ currentX: number;
35
+ currentY: number;
36
+ } | null;
37
+ type Updater<T> = T | ((prev: T) => T);
38
+ interface CanvasAreaProps {
39
+ /** @deprecated CanvasArea now uses React Flow's native viewport; this prop is kept for API compatibility. */
40
+ canvas: CanvasHandle;
41
+ groups: GenerationGroup[];
42
+ onGroupsChange: (update: Updater<GenerationGroup[]>) => void;
43
+ canvasImages: CanvasImage[];
44
+ onCanvasImagesChange: (update: Updater<CanvasImage[]>) => void;
45
+ selectedIds: Set<string>;
46
+ onSelectedIdsChange: (update: Updater<Set<string>>) => void;
47
+ toolMode: ToolMode;
48
+ spaceHeld: boolean;
49
+ /** @deprecated Rubber-band selection is handled by React Flow's built-in selection mode. */
50
+ rubberBand?: RubberBand;
51
+ /** @deprecated Rubber-band selection is handled by React Flow's built-in selection mode. */
52
+ setRubberBand?: (update: Updater<RubberBand>) => void;
53
+ draggingId: string | null;
54
+ setDraggingId: (id: string | null) => void;
55
+ draggingImageId: string | null;
56
+ setDraggingImageId: (id: string | null) => void;
57
+ pipelineStages?: Record<string, PipelineStatus>;
58
+ onAddComment: (draft: {
59
+ iterationId: string;
60
+ position: Point;
61
+ screenX: number;
62
+ screenY: number;
63
+ }) => void;
64
+ onClickComment: (comment: Comment, iterationId: string) => void;
65
+ onImageDrop?: (files: File[], dropX?: number, dropY?: number) => void;
66
+ onContextMenu?: (e: React.MouseEvent) => void;
67
+ emptyTitle?: string;
68
+ emptyDescription?: string;
69
+ toolbar?: React.ReactNode;
70
+ }
71
+ export declare const CanvasArea: ({ groups, onGroupsChange, canvasImages, onCanvasImagesChange, selectedIds, onSelectedIdsChange, toolMode, spaceHeld, draggingId: _draggingId, setDraggingId, draggingImageId: _draggingImageId, setDraggingImageId, pipelineStages, onAddComment, onClickComment, onImageDrop, onContextMenu, emptyTitle, emptyDescription, toolbar }: CanvasAreaProps) => import("react/jsx-runtime").JSX.Element;
72
+ //#endregion
73
+ //#region src/components/CanvasProvider.d.ts
74
+ interface CanvasProviderProps {
75
+ children: ReactNode;
76
+ }
77
+ export declare function CanvasProvider({ children }: CanvasProviderProps): import("react/jsx-runtime").JSX.Element;
78
+ //#endregion
79
+ //#region src/lib/frame-clipboard.d.ts
80
+ /** Frames data stored in the internal Jotai clipboard atom */
81
+ interface ClipboardFramesData {
82
+ type: "frames";
83
+ /** Groups (each group becomes a new group on paste) */
84
+ groups: GenerationGroup[];
85
+ /** Standalone canvas images copied with the frames */
86
+ images: CanvasImage[];
87
+ /** Relative positions (iteration id → {dx, dy} from bounding-box origin) */
88
+ relativePositions: Record<string, Point>;
89
+ /** Bounding-box origin of the original selection */
90
+ origin: Point;
91
+ createdAt: number;
92
+ }
93
+ /**
94
+ * Serializes selected iterations + their images into the internal clipboard format.
95
+ * Returns null if no iterations were selected.
96
+ */
97
+ export declare function copyFrames(selectedIds: Set<string>, groups: GenerationGroup[], images: CanvasImage[]): ClipboardFramesData | null;
98
+ /**
99
+ * Deserializes clipboard data and returns new groups + images positioned at viewportCenter.
100
+ * Assigns fresh unique IDs to all created entities.
101
+ */
102
+ export declare function pasteFrames(clipboardData: ClipboardFramesData, viewportCenter: Point): {
103
+ groups: GenerationGroup[];
104
+ images: CanvasImage[];
105
+ };
106
+ export declare function cutFrames(selectedIds: Set<string>, groups: GenerationGroup[], images: CanvasImage[]): {
107
+ clipboardData: ClipboardFramesData | null;
108
+ groups: GenerationGroup[];
109
+ images: CanvasImage[];
110
+ };
111
+ //#endregion
112
+ //#region src/state/canvas-atoms.d.ts
113
+ export declare const canvasOffsetAtom: import("jotai").PrimitiveAtom<Point> & {
114
+ init: Point;
115
+ };
116
+ export declare const canvasScaleAtom: import("jotai").PrimitiveAtom<number> & {
117
+ init: number;
118
+ };
119
+ export declare const isPanningAtom: import("jotai").PrimitiveAtom<boolean> & {
120
+ init: boolean;
121
+ };
122
+ //#endregion
123
+ //#region src/state/groups-atom.d.ts
124
+ export declare const groupsAtom: import("jotai").WritableAtom<GenerationGroup[], [update: GenerationGroup[] | ((prev: GenerationGroup[]) => GenerationGroup[])], void>;
125
+ export declare const resetSessionAtom: import("jotai").WritableAtom<null, [], void> & {
126
+ init: null;
127
+ };
128
+ export declare function hydrateGroups(setGroups: (g: GenerationGroup[] | ((p: GenerationGroup[]) => GenerationGroup[])) => void): Promise<void>;
129
+ //#endregion
130
+ //#region src/adapter/types.d.ts
131
+ /**
132
+ * Maps one ShapeRecord `type` onto a React Flow node. `ctx` is whatever
133
+ * render context the host passes to `<FlowCanvas data={ctx}>` — selection
134
+ * sets, mode flags, callbacks — and is handed to `data` for the node
135
+ * component.
136
+ */
137
+ interface ShapeView<C> {
138
+ /** React Flow `nodeTypes` key this record type renders as. */
139
+ nodeType: string;
140
+ position: (record: ShapeRecord) => {
141
+ x: number;
142
+ y: number;
143
+ };
144
+ size?: (record: ShapeRecord) => {
145
+ width?: number;
146
+ height?: number;
147
+ };
148
+ data: (record: ShapeRecord, ctx: C) => Record<string, unknown>;
149
+ selected?: (record: ShapeRecord, ctx: C) => boolean;
150
+ draggable?: (record: ShapeRecord, ctx: C) => boolean;
151
+ selectable?: (record: ShapeRecord, ctx: C) => boolean;
152
+ /**
153
+ * Props patch written back when the user drags this node. Defaults to
154
+ * `{ position }`.
155
+ */
156
+ writePosition?: (record: ShapeRecord, position: {
157
+ x: number;
158
+ y: number;
159
+ }) => Record<string, unknown>;
160
+ }
161
+ /** Maps one BindingRecord `type` onto a React Flow edge. */
162
+ interface BindingView<C> {
163
+ edge: (record: BindingRecord, ctx: C) => Partial<Edge>;
164
+ }
165
+ interface CanvasViews<C> {
166
+ shapes?: Record<string, ShapeView<C>>;
167
+ bindings?: Record<string, BindingView<C>>;
168
+ }
169
+ //#endregion
170
+ //#region src/adapter/FlowCanvas.d.ts
171
+ interface FlowCanvasProps<C> extends Omit<ReactFlowProps, "nodes" | "edges"> {
172
+ /** Record-type → React Flow node/edge views. */
173
+ views: CanvasViews<C>;
174
+ /** Opaque context handed to every view's data/selectors. */
175
+ data: C;
176
+ /** React Flow `nodeTypes` map (components live outside the adapter). */
177
+ nodeTypes: ReactFlowProps["nodeTypes"];
178
+ /**
179
+ * Called after RF changes have been translated into commands and applied
180
+ * to the store (origin "user"). Use it to keep host state (e.g. jotai
181
+ * persistence) in sync with position/removal changes.
182
+ */
183
+ onCommandsApplied?: (commands: Command[], changes: NodeChange[]) => void;
184
+ /** Selection is UI state, not document state — surfaced via callback. */
185
+ onSelectionChange?: ReactFlowProps["onSelectionChange"];
186
+ }
187
+ /**
188
+ * Generic store → React Flow adapter. The document is the source of truth;
189
+ * RF transient state (in-progress drags, selection) stays local.
190
+ */
191
+ export declare const FlowCanvas: <C>({ views, data, nodeTypes, onCommandsApplied, onSelectionChange, children, ...rfProps }: FlowCanvasProps<C>) => import("react/jsx-runtime").JSX.Element;
192
+ //#endregion
193
+ //#region src/adapter/projection.d.ts
194
+ interface CanvasProjection {
195
+ nodes: Node[];
196
+ edges: Edge[];
197
+ }
198
+ /**
199
+ * Projects the document into React Flow nodes/edges. Shapes render in
200
+ * fractional-index order (document order = paint order = z-order).
201
+ */
202
+ export declare const projectStore: <C>(store: CanvasStore, views: CanvasViews<C>, ctx: C) => CanvasProjection;
203
+ //#endregion
204
+ //#region src/adapter/store-context.d.ts
205
+ interface CanvasStoreProviderProps {
206
+ store?: CanvasStore;
207
+ children: ReactNode;
208
+ }
209
+ export declare const CanvasStoreProvider: ({ store, children }: CanvasStoreProviderProps) => import("react/jsx-runtime").JSX.Element;
210
+ /** The store scoped to the nearest provider, or null outside one. */
211
+ export declare const useCanvasStore: () => CanvasStore | null;
212
+ //#endregion
213
+ //#region src/adapter/use-canvas-nodes.d.ts
214
+ /**
215
+ * Live projection of the document into React Flow nodes/edges. Recomputes
216
+ * when the store emits `change` (or when `views`/`ctx` identity changes).
217
+ * Requires a store: pass one directly or render inside CanvasStoreProvider.
218
+ */
219
+ export declare const useCanvasNodes: <C>(views: CanvasViews<C>, ctx: C, storeOverride?: CanvasStore) => CanvasProjection;
220
+ //#endregion
221
+ //#region src/views.d.ts
222
+ export declare const DESIGN_FRAME_TYPE = "design-frame";
223
+ export declare const CANVAS_IMAGE_TYPE = "canvas-image";
224
+ /** Render context the app hands to `<FlowCanvas data={ctx}>`. */
225
+ interface CalcaCanvasContext {
226
+ isSelectMode: boolean;
227
+ isCommentMode: boolean;
228
+ selectedIds: Set<string>;
229
+ pipelineStages?: Record<string, PipelineStatus>;
230
+ onAddComment: (draft: {
231
+ iterationId: string;
232
+ position: Point;
233
+ screenX: number;
234
+ screenY: number;
235
+ }) => void;
236
+ onClickComment: (comment: Comment, iterationId: string) => void;
237
+ }
238
+ export declare const iterationToRecord: (iteration: DesignIteration, groupId: string, index: string) => ShapeRecord;
239
+ export declare const imageToRecord: (image: CanvasImage, index: string) => ShapeRecord;
240
+ export declare const calcaViews: CanvasViews<CalcaCanvasContext>;
241
+ //#endregion
242
+ export type { BindingView, CalcaCanvasContext, CanvasAreaProps, CanvasHandle, CanvasProjection, CanvasViews, ClipboardFramesData, ShapeView };
243
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":["Point"],"sources":["../src/hooks/use-canvas.ts","../src/components/CanvasArea.tsx","../src/components/CanvasProvider.tsx","../src/lib/frame-clipboard.ts","../src/state/canvas-atoms.ts","../src/state/groups-atom.ts","../src/adapter/types.ts","../src/adapter/FlowCanvas.tsx","../src/adapter/projection.ts","../src/adapter/store-context.tsx","../src/adapter/use-canvas-nodes.ts","../src/views.ts"],"mappings":"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"}
package/dist/index.mjs ADDED
@@ -0,0 +1,876 @@
1
+ import { Background, Panel, ReactFlow, ReactFlowProvider, SelectionMode, applyNodeChanges, useReactFlow, useViewport } from "@xyflow/react";
2
+ import "@xyflow/react/dist/style.css";
3
+ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
4
+ import { createCanvasStore, generateKeyBetween } from "@calca/canvas-base";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ import { DEFAULT_FRAME_WIDTH, DesignFrame, PipelineStatusOverlay } from "@calca/canvas-ui";
7
+ import { atom, useSetAtom } from "jotai";
8
+
9
+ //#region src/adapter/store-context.tsx
10
+ const CanvasStoreContext = createContext(null);
11
+ const CanvasStoreProvider = ({ store, children }) => {
12
+ const [owned] = useState(() => store ?? createCanvasStore());
13
+ return /* @__PURE__ */ jsx(CanvasStoreContext.Provider, {
14
+ value: owned,
15
+ children
16
+ });
17
+ };
18
+ /** The store scoped to the nearest provider, or null outside one. */
19
+ const useCanvasStore = () => useContext(CanvasStoreContext);
20
+
21
+ //#endregion
22
+ //#region src/adapter/projection.ts
23
+ /**
24
+ * Projects the document into React Flow nodes/edges. Shapes render in
25
+ * fractional-index order (document order = paint order = z-order).
26
+ */
27
+ const projectStore = (store, views, ctx) => {
28
+ const nodes = [];
29
+ const shapes = store.shapes().toSorted((a, b) => a.index < b.index ? -1 : 1);
30
+ for (const record of shapes) {
31
+ const view = views.shapes?.[record.type];
32
+ if (!view) continue;
33
+ nodes.push({
34
+ id: record.id,
35
+ type: view.nodeType,
36
+ position: view.position(record),
37
+ ...view.size?.(record),
38
+ data: view.data(record, ctx),
39
+ selected: view.selected?.(record, ctx) ?? false,
40
+ draggable: view.draggable?.(record, ctx) ?? true,
41
+ selectable: view.selectable?.(record, ctx) ?? true
42
+ });
43
+ }
44
+ return {
45
+ nodes,
46
+ edges: store.bindings().toSorted((a, b) => a.index < b.index ? -1 : 1).map((record) => Object.assign({
47
+ id: record.id,
48
+ source: record.fromId,
49
+ target: record.toId
50
+ }, views.bindings?.[record.type]?.edge(record, ctx)))
51
+ };
52
+ };
53
+
54
+ //#endregion
55
+ //#region src/adapter/use-canvas-nodes.ts
56
+ /**
57
+ * Live projection of the document into React Flow nodes/edges. Recomputes
58
+ * when the store emits `change` (or when `views`/`ctx` identity changes).
59
+ * Requires a store: pass one directly or render inside CanvasStoreProvider.
60
+ */
61
+ const useCanvasNodes = (views, ctx, storeOverride) => {
62
+ const contextStore = useCanvasStore();
63
+ const store = storeOverride ?? contextStore;
64
+ if (!store) throw new Error("useCanvasNodes requires a CanvasStore (prop or CanvasStoreProvider)");
65
+ const version = useRef(0);
66
+ const subscribe = useCallback((notify) => {
67
+ const listener = () => {
68
+ version.current += 1;
69
+ notify();
70
+ };
71
+ store.addEventListener("change", listener);
72
+ return () => store.removeEventListener("change", listener);
73
+ }, [store]);
74
+ const v = useSyncExternalStore(subscribe, () => version.current);
75
+ return useMemo(() => projectStore(store, views, ctx), [
76
+ store,
77
+ views,
78
+ ctx,
79
+ v
80
+ ]);
81
+ };
82
+
83
+ //#endregion
84
+ //#region src/adapter/FlowCanvas.tsx
85
+ /**
86
+ * Generic store → React Flow adapter. The document is the source of truth;
87
+ * RF transient state (in-progress drags, selection) stays local.
88
+ */
89
+ const FlowCanvas = ({ views, data, nodeTypes, onCommandsApplied, onSelectionChange, children, ...rfProps }) => {
90
+ const store = useCanvasStore();
91
+ if (!store) throw new Error("FlowCanvas must render inside a CanvasStoreProvider");
92
+ const projected = useCanvasNodes(views, data, store);
93
+ const [nodes, setNodes] = useState(projected.nodes);
94
+ useEffect(() => {
95
+ setNodes((prev) => {
96
+ const byId = new Map(prev.map((n) => [n.id, n]));
97
+ return projected.nodes.map((n) => {
98
+ const existing = byId.get(n.id);
99
+ if (!existing) return n;
100
+ return {
101
+ ...n,
102
+ position: existing.position,
103
+ selected: existing.selected,
104
+ data: {
105
+ ...n.data,
106
+ isDragging: existing.data?.isDragging ?? false
107
+ }
108
+ };
109
+ });
110
+ });
111
+ }, [projected.nodes]);
112
+ const shapeById = useMemo(() => {
113
+ const map = /* @__PURE__ */ new Map();
114
+ for (const s of store.shapes()) map.set(s.id, s);
115
+ return map;
116
+ }, [store, projected]);
117
+ const onNodesChange = useCallback((changes) => {
118
+ setNodes((prev) => applyNodeChanges(changes, prev));
119
+ const applied = [];
120
+ for (const change of changes) {
121
+ if (change.type !== "position" && change.type !== "remove") continue;
122
+ const record = shapeById.get(change.id);
123
+ if (!record) continue;
124
+ const view = views.shapes?.[record.type];
125
+ if (change.type === "position" && change.position) {
126
+ const props = view?.writePosition?.(record, change.position) ?? { position: change.position };
127
+ const command = {
128
+ op: "update",
129
+ id: change.id,
130
+ props
131
+ };
132
+ applied.push(command);
133
+ store.apply([command], {
134
+ origin: "user",
135
+ history: change.dragging === false
136
+ });
137
+ } else if (change.type === "remove") {
138
+ const command = {
139
+ op: "delete",
140
+ id: change.id
141
+ };
142
+ applied.push(command);
143
+ store.apply([command], { origin: "user" });
144
+ }
145
+ }
146
+ if (applied.length > 0) onCommandsApplied?.(applied, changes);
147
+ }, [
148
+ shapeById,
149
+ store,
150
+ views,
151
+ onCommandsApplied
152
+ ]);
153
+ const onConnect = useCallback((connection) => {
154
+ if (!connection.source || !connection.target) return;
155
+ const record = {
156
+ id: `binding-${crypto.randomUUID()}`,
157
+ type: "edge",
158
+ fromId: connection.source,
159
+ toId: connection.target,
160
+ index: generateKeyBetween(null, null),
161
+ props: {}
162
+ };
163
+ store.apply([{
164
+ op: "create-binding",
165
+ record
166
+ }], { origin: "user" });
167
+ }, [store]);
168
+ return /* @__PURE__ */ jsx(ReactFlow, {
169
+ nodes,
170
+ edges: projected.edges,
171
+ nodeTypes,
172
+ onNodesChange,
173
+ onConnect,
174
+ onSelectionChange,
175
+ ...rfProps,
176
+ children
177
+ });
178
+ };
179
+
180
+ //#endregion
181
+ //#region src/views.ts
182
+ const DESIGN_FRAME_TYPE = "design-frame";
183
+ const CANVAS_IMAGE_TYPE = "canvas-image";
184
+ const iterationToRecord = (iteration, groupId, index) => ({
185
+ id: iteration.id,
186
+ type: DESIGN_FRAME_TYPE,
187
+ parentId: null,
188
+ index,
189
+ props: {
190
+ position: iteration.position,
191
+ width: iteration.width,
192
+ height: iteration.height,
193
+ isLoading: iteration.isLoading,
194
+ groupId,
195
+ iteration
196
+ }
197
+ });
198
+ const imageToRecord = (image, index) => ({
199
+ id: image.id,
200
+ type: CANVAS_IMAGE_TYPE,
201
+ parentId: null,
202
+ index,
203
+ props: {
204
+ position: image.position,
205
+ width: image.width,
206
+ height: image.height,
207
+ image
208
+ }
209
+ });
210
+ const propsPoint = (record) => record.props.position;
211
+ const propsSize = (record) => {
212
+ const { width, height } = record.props;
213
+ return {
214
+ width,
215
+ height
216
+ };
217
+ };
218
+ const calcaViews = { shapes: {
219
+ [DESIGN_FRAME_TYPE]: {
220
+ nodeType: "designFrame",
221
+ position: propsPoint,
222
+ size: (record) => {
223
+ const { width, height, isLoading } = record.props;
224
+ return {
225
+ width: width ?? 480,
226
+ height: isLoading ? 320 : height ?? 320
227
+ };
228
+ },
229
+ data: (record, ctx) => {
230
+ const { groupId, iteration } = record.props;
231
+ return {
232
+ groupId,
233
+ isCommentMode: ctx.isCommentMode,
234
+ isDragging: false,
235
+ isSelectMode: ctx.isSelectMode,
236
+ iteration,
237
+ onAddComment: ctx.onAddComment,
238
+ onClickComment: ctx.onClickComment,
239
+ pipelineStatus: ctx.pipelineStages?.[record.id]
240
+ };
241
+ },
242
+ selected: (record, ctx) => ctx.selectedIds.has(record.id),
243
+ draggable: (_record, ctx) => ctx.isSelectMode,
244
+ selectable: (_record, ctx) => ctx.isSelectMode
245
+ },
246
+ [CANVAS_IMAGE_TYPE]: {
247
+ nodeType: "canvasImage",
248
+ position: propsPoint,
249
+ size: propsSize,
250
+ data: (record, ctx) => ({
251
+ image: record.props.image,
252
+ isDragging: false,
253
+ isSelectMode: ctx.isSelectMode,
254
+ isSelected: ctx.selectedIds.has(record.id)
255
+ }),
256
+ selected: (record, ctx) => ctx.selectedIds.has(record.id),
257
+ draggable: (_record, ctx) => ctx.isSelectMode,
258
+ selectable: (_record, ctx) => ctx.isSelectMode
259
+ }
260
+ } };
261
+
262
+ //#endregion
263
+ //#region src/components/nodes/CanvasImageNode.tsx
264
+ function CanvasImageNode({ data }) {
265
+ const { image, isSelectMode, isDragging, isSelected } = data;
266
+ return /* @__PURE__ */ jsxs("div", {
267
+ className: `group w-full h-full rounded-lg overflow-hidden shadow-md transition-shadow ${isSelected ? "ring-2 ring-blue-500 border-blue-400/50 shadow-lg" : "border border-white/40 hover:shadow-lg"} ${isSelectMode ? isDragging ? "cursor-grabbing shadow-xl ring-2 ring-blue-400/30" : "cursor-grab" : ""}`,
268
+ children: [/* @__PURE__ */ jsx("img", {
269
+ src: image.dataUrl,
270
+ alt: image.name,
271
+ className: "w-full h-full object-cover",
272
+ draggable: false
273
+ }), /* @__PURE__ */ jsx("span", {
274
+ className: "absolute bottom-1 left-1 right-1 text-[9px] text-white bg-black/50 rounded px-1 py-0.5 truncate opacity-0 group-hover:opacity-100 transition-opacity",
275
+ children: image.name
276
+ })]
277
+ });
278
+ }
279
+
280
+ //#endregion
281
+ //#region src/components/nodes/DesignFrameNode.tsx
282
+ function DesignFrameNode({ data, selected }) {
283
+ const { iteration, isCommentMode, isSelectMode, pipelineStatus, onAddComment, onClickComment } = data;
284
+ const { zoom } = useViewport();
285
+ const handleAddComment = (iterationId, position, screenX, screenY) => {
286
+ onAddComment({
287
+ iterationId,
288
+ position,
289
+ screenX,
290
+ screenY
291
+ });
292
+ };
293
+ const frameWidth = iteration.width || DEFAULT_FRAME_WIDTH;
294
+ const frameHeight = iteration.isLoading ? 320 : iteration.height || 320;
295
+ return /* @__PURE__ */ jsxs("div", {
296
+ style: { width: frameWidth },
297
+ children: [
298
+ /* @__PURE__ */ jsx("div", {
299
+ className: "mb-2 flex items-center gap-2 group/label pointer-events-none",
300
+ children: /* @__PURE__ */ jsx("span", {
301
+ className: "text-xs font-medium text-gray-500/80 bg-white/60 backdrop-blur-sm px-2.5 py-0.5 rounded-lg border border-white/40",
302
+ children: iteration.label
303
+ })
304
+ }),
305
+ /* @__PURE__ */ jsx(DesignFrame, {
306
+ iteration,
307
+ width: frameWidth,
308
+ isCommentMode,
309
+ isSelectMode,
310
+ isDragging: data.isDragging,
311
+ isSelected: selected,
312
+ scale: zoom,
313
+ onAddComment: handleAddComment,
314
+ onClickComment
315
+ }),
316
+ pipelineStatus && pipelineStatus.stage !== "done" && /* @__PURE__ */ jsx(PipelineStatusOverlay, {
317
+ status: pipelineStatus,
318
+ x: 0,
319
+ y: 0,
320
+ width: frameWidth,
321
+ frameHeight
322
+ })
323
+ ]
324
+ });
325
+ }
326
+
327
+ //#endregion
328
+ //#region src/components/CanvasArea.tsx
329
+ const nodeTypes = {
330
+ canvasImage: CanvasImageNode,
331
+ designFrame: DesignFrameNode
332
+ };
333
+ const propsEqual = (a, b) => {
334
+ const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
335
+ for (const key of keys) if (!Object.is(a[key], b[key])) return false;
336
+ return true;
337
+ };
338
+ const CanvasArea = ({ groups, onGroupsChange, canvasImages, onCanvasImagesChange, selectedIds, onSelectedIdsChange, toolMode, spaceHeld, draggingId: _draggingId, setDraggingId, draggingImageId: _draggingImageId, setDraggingImageId, pipelineStages, onAddComment, onClickComment, onImageDrop, onContextMenu, emptyTitle, emptyDescription, toolbar }) => {
339
+ const store = useCanvasStore();
340
+ if (!store) throw new Error("CanvasArea must render inside a CanvasStoreProvider");
341
+ const isSelectMode = toolMode === "select" && !spaceHeld;
342
+ const isCommentMode = toolMode === "comment" && !spaceHeld;
343
+ const prevRecords = useRef(/* @__PURE__ */ new Map());
344
+ useEffect(() => {
345
+ const next = /* @__PURE__ */ new Map();
346
+ const prev = prevRecords.current;
347
+ const indexOf = (id) => prev.get(id)?.index ?? store.nextIndex(null);
348
+ for (const group of groups) for (const iteration of group.iterations) next.set(iteration.id, iterationToRecord(iteration, group.id, indexOf(iteration.id)));
349
+ for (const image of canvasImages) next.set(image.id, imageToRecord(image, indexOf(image.id)));
350
+ const commands = [];
351
+ for (const [id, record] of next) {
352
+ const existing = prev.get(id);
353
+ if (!existing) commands.push({
354
+ op: "create-shape",
355
+ record
356
+ });
357
+ else if (!propsEqual(existing.props, record.props)) commands.push({
358
+ op: "update",
359
+ id,
360
+ props: record.props
361
+ });
362
+ }
363
+ for (const id of prev.keys()) if (!next.has(id)) commands.push({
364
+ op: "delete",
365
+ id
366
+ });
367
+ prevRecords.current = next;
368
+ if (commands.length > 0) store.apply(commands, {
369
+ origin: "remote",
370
+ history: false
371
+ });
372
+ }, [
373
+ groups,
374
+ canvasImages,
375
+ store
376
+ ]);
377
+ const onCommandsApplied = useCallback((commands, _changes) => {
378
+ const positionUpdates = /* @__PURE__ */ new Map();
379
+ const removedIds = /* @__PURE__ */ new Set();
380
+ for (const command of commands) if (command.op === "update" && typeof command.props.position === "object") positionUpdates.set(command.id, command.props.position);
381
+ else if (command.op === "delete") removedIds.add(command.id);
382
+ if (positionUpdates.size > 0) {
383
+ onGroupsChange((prevGroups) => prevGroups.map((group) => ({
384
+ ...group,
385
+ iterations: group.iterations.map((iter) => {
386
+ const update = positionUpdates.get(iter.id);
387
+ return update ? {
388
+ ...iter,
389
+ position: update
390
+ } : iter;
391
+ })
392
+ })));
393
+ onCanvasImagesChange((prevImages) => prevImages.map((img) => {
394
+ const update = positionUpdates.get(img.id);
395
+ return update ? {
396
+ ...img,
397
+ position: update
398
+ } : img;
399
+ }));
400
+ }
401
+ if (removedIds.size > 0) {
402
+ onGroupsChange((prevGroups) => prevGroups.map((group) => ({
403
+ ...group,
404
+ iterations: group.iterations.filter((iter) => !removedIds.has(iter.id))
405
+ })).filter((group) => group.iterations.length > 0));
406
+ onCanvasImagesChange((prevImages) => prevImages.filter((img) => !removedIds.has(img.id)));
407
+ }
408
+ }, [onGroupsChange, onCanvasImagesChange]);
409
+ const onNodeDragStart = useCallback((_event, node) => {
410
+ if (canvasImages.some((img) => img.id === node.id)) setDraggingImageId(node.id);
411
+ else setDraggingId(node.id);
412
+ }, [
413
+ canvasImages,
414
+ setDraggingId,
415
+ setDraggingImageId
416
+ ]);
417
+ const onNodeDragStop = useCallback(() => {
418
+ setDraggingId(null);
419
+ setDraggingImageId(null);
420
+ }, [setDraggingId, setDraggingImageId]);
421
+ const onSelectionChange = useCallback(({ nodes: selectedNodes }) => {
422
+ onSelectedIdsChange(new Set(selectedNodes.map((n) => n.id)));
423
+ }, [onSelectedIdsChange]);
424
+ const onPaneClick = useCallback(() => {
425
+ onSelectedIdsChange(/* @__PURE__ */ new Set());
426
+ }, [onSelectedIdsChange]);
427
+ const handleDragOver = useCallback((e) => {
428
+ e.preventDefault();
429
+ e.dataTransfer.dropEffect = "copy";
430
+ }, []);
431
+ const handleDrop = useCallback((e) => {
432
+ e.preventDefault();
433
+ const files = [...e.dataTransfer.files].filter((f) => f.type.startsWith("image/"));
434
+ if (files.length > 0 && onImageDrop) onImageDrop(files, e.clientX, e.clientY);
435
+ }, [onImageDrop]);
436
+ const handleContextMenu = useCallback((e) => {
437
+ if (onContextMenu) {
438
+ e.preventDefault();
439
+ onContextMenu(e);
440
+ }
441
+ }, [onContextMenu]);
442
+ const ctx = useMemo(() => ({
443
+ isSelectMode,
444
+ isCommentMode,
445
+ selectedIds,
446
+ pipelineStages,
447
+ onAddComment,
448
+ onClickComment
449
+ }), [
450
+ isSelectMode,
451
+ isCommentMode,
452
+ selectedIds,
453
+ pipelineStages,
454
+ onAddComment,
455
+ onClickComment
456
+ ]);
457
+ return /* @__PURE__ */ jsxs("div", {
458
+ className: "absolute inset-0 canvas-dots",
459
+ onDragOver: handleDragOver,
460
+ onDrop: handleDrop,
461
+ onContextMenu: handleContextMenu,
462
+ children: [
463
+ /* @__PURE__ */ jsxs(FlowCanvas, {
464
+ views: calcaViews,
465
+ data: ctx,
466
+ nodeTypes,
467
+ onCommandsApplied,
468
+ onSelectionChange,
469
+ onPaneClick,
470
+ onNodeDragStart,
471
+ onNodeDragStop,
472
+ fitView: true,
473
+ fitViewOptions: { padding: .2 },
474
+ minZoom: .1,
475
+ maxZoom: 5,
476
+ panOnScroll: true,
477
+ selectionOnDrag: isSelectMode,
478
+ selectionMode: SelectionMode.Partial,
479
+ panOnDrag: spaceHeld ? [
480
+ 0,
481
+ 1,
482
+ 2
483
+ ] : false,
484
+ nodesDraggable: isSelectMode,
485
+ elementsSelectable: isSelectMode,
486
+ selectNodesOnDrag: isSelectMode,
487
+ multiSelectionKeyCode: "Shift",
488
+ deleteKeyCode: null,
489
+ proOptions: { hideAttribution: true },
490
+ className: isCommentMode ? "cursor-crosshair" : "cursor-default",
491
+ children: [/* @__PURE__ */ jsx(Background, {
492
+ gap: 20,
493
+ size: 1,
494
+ color: "#e5e7eb"
495
+ }), toolbar && /* @__PURE__ */ jsx(Panel, {
496
+ position: "top-center",
497
+ style: { zIndex: 1001 },
498
+ children: toolbar
499
+ })]
500
+ }),
501
+ groups.length === 0 && canvasImages.length === 0 && emptyTitle && /* @__PURE__ */ jsx("div", {
502
+ className: "absolute inset-0 flex flex-col items-center justify-center pointer-events-none z-10",
503
+ children: /* @__PURE__ */ jsxs("div", {
504
+ className: "text-center",
505
+ children: [/* @__PURE__ */ jsx("h1", {
506
+ className: "text-2xl font-semibold text-gray-300 mb-2",
507
+ children: emptyTitle
508
+ }), emptyDescription && /* @__PURE__ */ jsx("p", {
509
+ className: "text-gray-400/70 text-sm",
510
+ children: emptyDescription
511
+ })]
512
+ })
513
+ }),
514
+ /* @__PURE__ */ jsx("style", { children: `
515
+ .react-flow__node-designFrame,
516
+ .react-flow__node-canvasImage {
517
+ border: none !important;
518
+ background: transparent !important;
519
+ padding: 0 !important;
520
+ overflow: visible !important;
521
+ }
522
+ .react-flow__node-designFrame.selected,
523
+ .react-flow__node-canvasImage.selected {
524
+ box-shadow: none !important;
525
+ }
526
+ .react-flow__node-designFrame .react-flow__handle {
527
+ opacity: 0;
528
+ }
529
+ ` })
530
+ ]
531
+ });
532
+ };
533
+
534
+ //#endregion
535
+ //#region src/components/CanvasProvider.tsx
536
+ function CanvasProvider({ children }) {
537
+ return /* @__PURE__ */ jsx(CanvasStoreProvider, { children: /* @__PURE__ */ jsx(ReactFlowProvider, { children }) });
538
+ }
539
+
540
+ //#endregion
541
+ //#region src/state/canvas-atoms.ts
542
+ const canvasOffsetAtom = atom({
543
+ x: 0,
544
+ y: 0
545
+ });
546
+ const canvasScaleAtom = atom(1);
547
+ const isPanningAtom = atom(false);
548
+
549
+ //#endregion
550
+ //#region src/hooks/use-canvas.ts
551
+ function useCanvasViewport() {
552
+ const { x, y, zoom } = useViewport();
553
+ const { setViewport, fitView } = useReactFlow();
554
+ const setCanvasOffset = useSetAtom(canvasOffsetAtom);
555
+ const setCanvasScale = useSetAtom(canvasScaleAtom);
556
+ useEffect(() => {
557
+ setCanvasOffset({
558
+ x,
559
+ y
560
+ });
561
+ setCanvasScale(zoom);
562
+ }, [
563
+ x,
564
+ y,
565
+ zoom,
566
+ setCanvasOffset,
567
+ setCanvasScale
568
+ ]);
569
+ const resetView = useCallback(() => {
570
+ setViewport({
571
+ x: 0,
572
+ y: 0,
573
+ zoom: 1
574
+ });
575
+ }, [setViewport]);
576
+ const zoomIn = useCallback(() => {
577
+ setViewport({
578
+ x,
579
+ y,
580
+ zoom: Math.min(zoom * 1.2, 5)
581
+ });
582
+ }, [
583
+ setViewport,
584
+ x,
585
+ y,
586
+ zoom
587
+ ]);
588
+ const zoomOut = useCallback(() => {
589
+ setViewport({
590
+ x,
591
+ y,
592
+ zoom: Math.max(zoom * .8, .1)
593
+ });
594
+ }, [
595
+ setViewport,
596
+ x,
597
+ y,
598
+ zoom
599
+ ]);
600
+ const fitToView = useCallback(() => {
601
+ fitView({
602
+ padding: .2,
603
+ duration: 300
604
+ });
605
+ }, [fitView]);
606
+ const zoomToFit = useCallback((bounds) => {
607
+ const contentW = bounds.maxX - bounds.minX;
608
+ const contentH = bounds.maxY - bounds.minY;
609
+ if (contentW <= 0 || contentH <= 0) return;
610
+ const vw = window.innerWidth;
611
+ const vh = window.innerHeight;
612
+ const scaleX = (vw - 160) / contentW;
613
+ const scaleY = (vh - 160) / contentH;
614
+ const nextZoom = Math.min(scaleX, scaleY, 1);
615
+ const centerX = (bounds.minX + bounds.maxX) / 2;
616
+ const centerY = (bounds.minY + bounds.maxY) / 2;
617
+ setViewport({
618
+ x: vw / 2 - centerX * nextZoom,
619
+ y: vh / 2 - centerY * nextZoom,
620
+ zoom: nextZoom
621
+ });
622
+ }, [setViewport]);
623
+ const screenToCanvas = useCallback((screenX, screenY, rect) => ({
624
+ x: (screenX - rect.left - x) / zoom,
625
+ y: (screenY - rect.top - y) / zoom
626
+ }), [
627
+ x,
628
+ y,
629
+ zoom
630
+ ]);
631
+ return {
632
+ offset: {
633
+ x,
634
+ y
635
+ },
636
+ scale: zoom,
637
+ resetView,
638
+ zoomIn,
639
+ zoomOut,
640
+ fitToView,
641
+ zoomToFit,
642
+ screenToCanvas
643
+ };
644
+ }
645
+ /** Backward-compatible alias for code that still imports `useCanvas`. */
646
+ const useCanvas = useCanvasViewport;
647
+
648
+ //#endregion
649
+ //#region src/lib/frame-clipboard.ts
650
+ /**
651
+ * Serializes selected iterations + their images into the internal clipboard format.
652
+ * Returns null if no iterations were selected.
653
+ */
654
+ function copyFrames(selectedIds, groups, images) {
655
+ if (selectedIds.size === 0) return null;
656
+ const selectedIterationsByGroup = /* @__PURE__ */ new Map();
657
+ for (const group of groups) {
658
+ const selectedIters = group.iterations.filter((iter) => selectedIds.has(iter.id));
659
+ if (selectedIters.length > 0) selectedIterationsByGroup.set(group.id, {
660
+ ...group,
661
+ iterations: selectedIters
662
+ });
663
+ }
664
+ if (selectedIterationsByGroup.size === 0) return null;
665
+ const selectedImages = images.filter((img) => selectedIds.has(img.id));
666
+ let minX = Infinity;
667
+ let minY = Infinity;
668
+ for (const [, group] of selectedIterationsByGroup) for (const iter of group.iterations) {
669
+ if (iter.position.x < minX) minX = iter.position.x;
670
+ if (iter.position.y < minY) minY = iter.position.y;
671
+ }
672
+ const origin = {
673
+ x: minX,
674
+ y: minY
675
+ };
676
+ const relativePositions = {};
677
+ for (const [, group] of selectedIterationsByGroup) for (const iter of group.iterations) relativePositions[iter.id] = {
678
+ x: iter.position.x - origin.x,
679
+ y: iter.position.y - origin.y
680
+ };
681
+ return {
682
+ type: "frames",
683
+ groups: Array.from(selectedIterationsByGroup.values()),
684
+ images: selectedImages,
685
+ relativePositions,
686
+ origin,
687
+ createdAt: Date.now()
688
+ };
689
+ }
690
+ /**
691
+ * Deserializes clipboard data and returns new groups + images positioned at viewportCenter.
692
+ * Assigns fresh unique IDs to all created entities.
693
+ */
694
+ function pasteFrames(clipboardData, viewportCenter) {
695
+ const now = Date.now();
696
+ const newGroups = [];
697
+ const newImages = [];
698
+ for (const group of clipboardData.groups) {
699
+ const newGroupId = `${group.id}-paste-${now}`;
700
+ const newIterations = group.iterations.map((iter) => {
701
+ const rel = clipboardData.relativePositions[iter.id] ?? {
702
+ x: 0,
703
+ y: 0
704
+ };
705
+ return {
706
+ ...iter,
707
+ id: `${iter.id}-paste-${now}`,
708
+ position: {
709
+ x: viewportCenter.x + rel.x,
710
+ y: viewportCenter.y + rel.y
711
+ }
712
+ };
713
+ });
714
+ newGroups.push({
715
+ ...group,
716
+ id: newGroupId,
717
+ iterations: newIterations,
718
+ createdAt: now
719
+ });
720
+ for (const img of clipboardData.images) {
721
+ const rel = clipboardData.relativePositions[img.id] ?? {
722
+ x: 0,
723
+ y: 0
724
+ };
725
+ newImages.push({
726
+ ...img,
727
+ id: `${img.id}-paste-${now}`,
728
+ position: {
729
+ x: viewportCenter.x + rel.x,
730
+ y: viewportCenter.y + rel.y
731
+ }
732
+ });
733
+ }
734
+ }
735
+ return {
736
+ groups: newGroups,
737
+ images: newImages
738
+ };
739
+ }
740
+ function cutFrames(selectedIds, groups, images) {
741
+ return {
742
+ clipboardData: copyFrames(selectedIds, groups, images),
743
+ groups: groups.map((g) => ({
744
+ ...g,
745
+ iterations: g.iterations.filter((iter) => !selectedIds.has(iter.id))
746
+ })).filter((g) => g.iterations.length > 0),
747
+ images: images.filter((img) => !selectedIds.has(img.id))
748
+ };
749
+ }
750
+
751
+ //#endregion
752
+ //#region src/state/groups-atom.ts
753
+ const STORAGE_KEY = "calca-canvas-session";
754
+ const IMG_DB_NAME = "calca-canvas-images";
755
+ const IMG_STORE = "images";
756
+ function openImgDB() {
757
+ return new Promise((resolve, reject) => {
758
+ const req = indexedDB.open(IMG_DB_NAME, 1);
759
+ req.onupgradeneeded = () => req.result.createObjectStore(IMG_STORE);
760
+ req.onsuccess = () => resolve(req.result);
761
+ req.onerror = () => reject(req.error);
762
+ });
763
+ }
764
+ async function saveImagesToIDB(images) {
765
+ const db = await openImgDB();
766
+ const tx = db.transaction(IMG_STORE, "readwrite");
767
+ const store = tx.objectStore(IMG_STORE);
768
+ for (const [key, val] of Object.entries(images)) store.put(val, key);
769
+ return new Promise((resolve, reject) => {
770
+ tx.oncomplete = () => {
771
+ db.close();
772
+ resolve();
773
+ };
774
+ tx.onerror = () => {
775
+ db.close();
776
+ reject(tx.error);
777
+ };
778
+ });
779
+ }
780
+ async function loadImagesFromIDB() {
781
+ const db = await openImgDB();
782
+ const store = db.transaction(IMG_STORE, "readonly").objectStore(IMG_STORE);
783
+ const result = {};
784
+ return new Promise((resolve, reject) => {
785
+ const cursor = store.openCursor();
786
+ cursor.onsuccess = () => {
787
+ const c = cursor.result;
788
+ if (c) {
789
+ result[c.key] = c.value;
790
+ c.continue();
791
+ } else {
792
+ db.close();
793
+ resolve(result);
794
+ }
795
+ };
796
+ cursor.onerror = () => {
797
+ db.close();
798
+ reject(cursor.error);
799
+ };
800
+ });
801
+ }
802
+ function extractBase64(groups) {
803
+ const images = {};
804
+ let counter = 0;
805
+ return {
806
+ images,
807
+ stripped: groups.map((g) => ({
808
+ ...g,
809
+ iterations: g.iterations.map((it) => ({
810
+ ...it,
811
+ html: it.html ? it.html.replace(/src="(data:image\/[^"]+)"/g, (_m, uri) => {
812
+ const key = `img_${g.id}_${it.id}_${counter++}`;
813
+ images[key] = uri;
814
+ return `src="[idb:${key}]"`;
815
+ }) : it.html
816
+ }))
817
+ }))
818
+ };
819
+ }
820
+ function restoreBase64(groups, images) {
821
+ return groups.map((g) => ({
822
+ ...g,
823
+ iterations: g.iterations.map((it) => ({
824
+ ...it,
825
+ html: it.html ? it.html.replace(/src="\[idb:([^\]]+)\]"/g, (_m, key) => images[key] ? `src="${images[key]}"` : "src=\"[img-missing]\"") : it.html
826
+ }))
827
+ }));
828
+ }
829
+ let saveTimer = null;
830
+ function debouncedPersist(groups) {
831
+ if (saveTimer) clearTimeout(saveTimer);
832
+ saveTimer = setTimeout(() => {
833
+ try {
834
+ const { stripped, images } = extractBase64(groups.filter((g) => g.iterations.some((it) => it.html && !it.isLoading)));
835
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(stripped));
836
+ if (Object.keys(images).length > 0) saveImagesToIDB(images).catch((error) => console.debug("Failed to save images to IndexedDB", { error: error instanceof Error ? error.message : String(error) }));
837
+ } catch (error) {
838
+ console.debug("Failed to save canvas session", { error: error instanceof Error ? error.message : String(error) });
839
+ }
840
+ }, 500);
841
+ }
842
+ const _groupsBase = atom([]);
843
+ const groupsAtom = atom((get) => get(_groupsBase), (_get, set, update) => {
844
+ set(_groupsBase, (prev) => {
845
+ const next = typeof update === "function" ? update(prev) : update;
846
+ debouncedPersist(next);
847
+ return next;
848
+ });
849
+ });
850
+ const resetSessionAtom = atom(null, (_get, set) => {
851
+ set(groupsAtom, []);
852
+ try {
853
+ localStorage.removeItem(STORAGE_KEY);
854
+ } catch {}
855
+ });
856
+ async function hydrateGroups(setGroups) {
857
+ try {
858
+ const raw = localStorage.getItem(STORAGE_KEY);
859
+ if (raw) {
860
+ const valid = JSON.parse(raw).filter((g) => g.iterations.some((it) => it.html && !it.isLoading));
861
+ if (valid.length > 0) {
862
+ try {
863
+ const restored = restoreBase64(valid, await loadImagesFromIDB());
864
+ setGroups((prev) => prev.length === 0 ? restored : prev);
865
+ } catch {
866
+ setGroups((prev) => prev.length === 0 ? valid : prev);
867
+ }
868
+ return;
869
+ }
870
+ }
871
+ } catch {}
872
+ }
873
+
874
+ //#endregion
875
+ export { CANVAS_IMAGE_TYPE, CanvasArea, CanvasProvider, CanvasStoreProvider, DESIGN_FRAME_TYPE, FlowCanvas, calcaViews, canvasOffsetAtom, canvasScaleAtom, copyFrames, cutFrames, groupsAtom, hydrateGroups, imageToRecord, isPanningAtom, iterationToRecord, pasteFrames, projectStore, resetSessionAtom, useCanvas, useCanvasNodes, useCanvasStore, useCanvasViewport };
876
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/adapter/store-context.tsx","../src/adapter/projection.ts","../src/adapter/use-canvas-nodes.ts","../src/adapter/FlowCanvas.tsx","../src/views.ts","../src/components/nodes/CanvasImageNode.tsx","../src/components/nodes/DesignFrameNode.tsx","../src/components/CanvasArea.tsx","../src/components/CanvasProvider.tsx","../src/state/canvas-atoms.ts","../src/hooks/use-canvas.ts","../src/lib/frame-clipboard.ts","../src/state/groups-atom.ts"],"sourcesContent":["import { createCanvasStore, type CanvasStore } from \"@calca/canvas-base\";\nimport { createContext, useContext, useState, type ReactNode } from \"react\";\n\nconst CanvasStoreContext = createContext<CanvasStore | null>(null);\n\ninterface CanvasStoreProviderProps {\n store?: CanvasStore;\n children: ReactNode;\n}\n\nexport const CanvasStoreProvider = ({ store, children }: CanvasStoreProviderProps) => {\n const [owned] = useState(() => store ?? createCanvasStore());\n return <CanvasStoreContext.Provider value={owned}>{children}</CanvasStoreContext.Provider>;\n};\n\n/** The store scoped to the nearest provider, or null outside one. */\nexport const useCanvasStore = () => useContext(CanvasStoreContext);\n","import type { CanvasStore } from \"@calca/canvas-base\";\nimport type { Edge, Node } from \"@xyflow/react\";\n\nimport type { CanvasViews } from \"./types\";\n\nexport interface CanvasProjection {\n nodes: Node[];\n edges: Edge[];\n}\n\n/**\n * Projects the document into React Flow nodes/edges. Shapes render in\n * fractional-index order (document order = paint order = z-order).\n */\nexport const projectStore = <C>(\n store: CanvasStore,\n views: CanvasViews<C>,\n ctx: C,\n): CanvasProjection => {\n const nodes: Node[] = [];\n const shapes = store.shapes().toSorted((a, b) => (a.index < b.index ? -1 : 1));\n for (const record of shapes) {\n const view = views.shapes?.[record.type];\n if (!view) continue;\n nodes.push({\n id: record.id,\n type: view.nodeType,\n position: view.position(record),\n ...view.size?.(record),\n data: view.data(record, ctx),\n selected: view.selected?.(record, ctx) ?? false,\n draggable: view.draggable?.(record, ctx) ?? true,\n selectable: view.selectable?.(record, ctx) ?? true,\n });\n }\n\n const edges: Edge[] = store\n .bindings()\n .toSorted((a, b) => (a.index < b.index ? -1 : 1))\n .map((record) =>\n Object.assign(\n {\n id: record.id,\n source: record.fromId,\n target: record.toId,\n },\n views.bindings?.[record.type]?.edge(record, ctx),\n ),\n );\n\n return { nodes, edges };\n};\n","import type { CanvasStore } from \"@calca/canvas-base\";\nimport { useCallback, useMemo, useRef, useSyncExternalStore } from \"react\";\n\nimport { projectStore, type CanvasProjection } from \"./projection\";\nimport { useCanvasStore } from \"./store-context\";\nimport type { CanvasViews } from \"./types\";\n\n/**\n * Live projection of the document into React Flow nodes/edges. Recomputes\n * when the store emits `change` (or when `views`/`ctx` identity changes).\n * Requires a store: pass one directly or render inside CanvasStoreProvider.\n */\nexport const useCanvasNodes = <C>(\n views: CanvasViews<C>,\n ctx: C,\n storeOverride?: CanvasStore,\n): CanvasProjection => {\n const contextStore = useCanvasStore();\n const store = storeOverride ?? contextStore;\n if (!store) {\n throw new Error(\"useCanvasNodes requires a CanvasStore (prop or CanvasStoreProvider)\");\n }\n\n const version = useRef(0);\n const subscribe = useCallback(\n (notify: () => void) => {\n const listener = () => {\n version.current += 1;\n notify();\n };\n store.addEventListener(\"change\", listener);\n return () => store.removeEventListener(\"change\", listener);\n },\n [store],\n );\n const v = useSyncExternalStore(subscribe, () => version.current);\n\n return useMemo(\n () => projectStore(store, views, ctx),\n // v is the projection trigger; store/views/ctx cover the inputs.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [store, views, ctx, v],\n );\n};\n","import type { BindingRecord, Command, ShapeRecord } from \"@calca/canvas-base\";\nimport { generateKeyBetween } from \"@calca/canvas-base\";\nimport {\n applyNodeChanges,\n ReactFlow,\n type Connection,\n type Node,\n type NodeChange,\n type ReactFlowProps,\n} from \"@xyflow/react\";\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\n\nimport { useCanvasStore } from \"./store-context\";\nimport type { CanvasViews } from \"./types\";\nimport { useCanvasNodes } from \"./use-canvas-nodes\";\n\ninterface FlowCanvasProps<C> extends Omit<ReactFlowProps, \"nodes\" | \"edges\"> {\n /** Record-type → React Flow node/edge views. */\n views: CanvasViews<C>;\n /** Opaque context handed to every view's data/selectors. */\n data: C;\n /** React Flow `nodeTypes` map (components live outside the adapter). */\n nodeTypes: ReactFlowProps[\"nodeTypes\"];\n /**\n * Called after RF changes have been translated into commands and applied\n * to the store (origin \"user\"). Use it to keep host state (e.g. jotai\n * persistence) in sync with position/removal changes.\n */\n onCommandsApplied?: (commands: Command[], changes: NodeChange[]) => void;\n /** Selection is UI state, not document state — surfaced via callback. */\n onSelectionChange?: ReactFlowProps[\"onSelectionChange\"];\n}\n\n/**\n * Generic store → React Flow adapter. The document is the source of truth;\n * RF transient state (in-progress drags, selection) stays local.\n */\nexport const FlowCanvas = <C,>({\n views,\n data,\n nodeTypes,\n onCommandsApplied,\n onSelectionChange,\n children,\n ...rfProps\n}: FlowCanvasProps<C>) => {\n const store = useCanvasStore();\n if (!store) {\n throw new Error(\"FlowCanvas must render inside a CanvasStoreProvider\");\n }\n\n const projected = useCanvasNodes(views, data, store);\n const [nodes, setNodes] = useState<Node[]>(projected.nodes);\n\n // Re-sync from the document while preserving RF transient drag/selection.\n useEffect(() => {\n setNodes((prev) => {\n const byId = new Map(prev.map((n) => [n.id, n]));\n return projected.nodes.map((n) => {\n const existing = byId.get(n.id);\n if (!existing) return n;\n return {\n ...n,\n position: existing.position,\n selected: existing.selected,\n data: { ...n.data, isDragging: existing.data?.isDragging ?? false },\n };\n });\n });\n }, [projected.nodes]);\n\n const shapeById = useMemo(() => {\n const map = new Map<string, ShapeRecord>();\n for (const s of store.shapes()) map.set(s.id, s);\n return map;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [store, projected]);\n\n const onNodesChange = useCallback(\n (changes: NodeChange[]) => {\n setNodes((prev) => applyNodeChanges(changes, prev));\n\n const applied: Command[] = [];\n for (const change of changes) {\n if (change.type !== \"position\" && change.type !== \"remove\") continue;\n const record = shapeById.get(change.id);\n if (!record) continue;\n const view = views.shapes?.[record.type];\n if (change.type === \"position\" && change.position) {\n const props = view?.writePosition?.(record, change.position) ?? {\n position: change.position,\n };\n const command: Command = { op: \"update\", id: change.id, props };\n applied.push(command);\n // Intermediate drag positions stay out of undo history.\n store.apply([command], { origin: \"user\", history: change.dragging === false });\n } else if (change.type === \"remove\") {\n const command: Command = { op: \"delete\", id: change.id };\n applied.push(command);\n store.apply([command], { origin: \"user\" });\n }\n }\n\n if (applied.length > 0) onCommandsApplied?.(applied, changes);\n },\n [shapeById, store, views, onCommandsApplied],\n );\n\n const onConnect = useCallback(\n (connection: Connection) => {\n if (!connection.source || !connection.target) return;\n const record: BindingRecord = {\n id: `binding-${crypto.randomUUID()}`,\n type: \"edge\",\n fromId: connection.source,\n toId: connection.target,\n index: generateKeyBetween(null, null),\n props: {},\n };\n store.apply([{ op: \"create-binding\", record }], { origin: \"user\" });\n },\n [store],\n );\n\n return (\n <ReactFlow\n nodes={nodes}\n edges={projected.edges}\n nodeTypes={nodeTypes}\n onNodesChange={onNodesChange}\n onConnect={onConnect}\n onSelectionChange={onSelectionChange}\n {...rfProps}\n >\n {children}\n </ReactFlow>\n );\n};\n","import type { ShapeRecord } from \"@calca/canvas-base\";\nimport type { CanvasImage, Comment, DesignIteration, PipelineStatus, Point } from \"@calca/shared\";\n\nimport type { CanvasViews } from \"./adapter\";\n\nexport const DESIGN_FRAME_TYPE = \"design-frame\";\nexport const CANVAS_IMAGE_TYPE = \"canvas-image\";\n\n/** Render context the app hands to `<FlowCanvas data={ctx}>`. */\nexport interface CalcaCanvasContext {\n isSelectMode: boolean;\n isCommentMode: boolean;\n selectedIds: Set<string>;\n pipelineStages?: Record<string, PipelineStatus>;\n onAddComment: (draft: {\n iterationId: string;\n position: Point;\n screenX: number;\n screenY: number;\n }) => void;\n onClickComment: (comment: Comment, iterationId: string) => void;\n}\n\nexport const iterationToRecord = (\n iteration: DesignIteration,\n groupId: string,\n index: string,\n): ShapeRecord => ({\n id: iteration.id,\n type: DESIGN_FRAME_TYPE,\n parentId: null,\n index,\n props: {\n position: iteration.position,\n width: iteration.width,\n height: iteration.height,\n isLoading: iteration.isLoading,\n groupId,\n iteration,\n },\n});\n\nexport const imageToRecord = (image: CanvasImage, index: string): ShapeRecord => ({\n id: image.id,\n type: CANVAS_IMAGE_TYPE,\n parentId: null,\n index,\n props: {\n position: image.position,\n width: image.width,\n height: image.height,\n image,\n },\n});\n\nconst propsPoint = (record: ShapeRecord) => (record.props as { position: Point }).position;\n\nconst propsSize = (record: ShapeRecord) => {\n const { width, height } = record.props as { width?: number; height?: number };\n return { width, height };\n};\n\nexport const calcaViews: CanvasViews<CalcaCanvasContext> = {\n shapes: {\n [DESIGN_FRAME_TYPE]: {\n nodeType: \"designFrame\",\n position: propsPoint,\n size: (record) => {\n const { width, height, isLoading } = record.props as {\n width?: number;\n height?: number;\n isLoading?: boolean;\n };\n return {\n width: width ?? 480,\n height: isLoading ? 320 : (height ?? 320),\n };\n },\n data: (record, ctx) => {\n const { groupId, iteration } = record.props as {\n groupId: string;\n iteration: DesignIteration;\n };\n return {\n groupId,\n isCommentMode: ctx.isCommentMode,\n isDragging: false,\n isSelectMode: ctx.isSelectMode,\n iteration,\n onAddComment: ctx.onAddComment,\n onClickComment: ctx.onClickComment,\n pipelineStatus: ctx.pipelineStages?.[record.id],\n };\n },\n selected: (record, ctx) => ctx.selectedIds.has(record.id),\n draggable: (_record, ctx) => ctx.isSelectMode,\n selectable: (_record, ctx) => ctx.isSelectMode,\n },\n [CANVAS_IMAGE_TYPE]: {\n nodeType: \"canvasImage\",\n position: propsPoint,\n size: propsSize,\n data: (record, ctx) => ({\n image: (record.props as { image: CanvasImage }).image,\n isDragging: false,\n isSelectMode: ctx.isSelectMode,\n isSelected: ctx.selectedIds.has(record.id),\n }),\n selected: (record, ctx) => ctx.selectedIds.has(record.id),\n draggable: (_record, ctx) => ctx.isSelectMode,\n selectable: (_record, ctx) => ctx.isSelectMode,\n },\n },\n};\n","import type { CanvasImage } from \"@calca/shared\";\nimport type { Node, NodeProps } from \"@xyflow/react\";\n\nexport type CanvasImageNodeData = {\n image: CanvasImage;\n isSelectMode: boolean;\n isDragging: boolean;\n isSelected: boolean;\n};\n\nexport type CanvasImageNodeType = Node<CanvasImageNodeData, \"canvasImage\">;\n\nexport function CanvasImageNode({ data }: NodeProps<CanvasImageNodeType>) {\n const { image, isSelectMode, isDragging, isSelected } = data;\n\n return (\n <div\n className={`group w-full h-full rounded-lg overflow-hidden shadow-md transition-shadow ${\n isSelected\n ? \"ring-2 ring-blue-500 border-blue-400/50 shadow-lg\"\n : \"border border-white/40 hover:shadow-lg\"\n } ${\n isSelectMode\n ? isDragging\n ? \"cursor-grabbing shadow-xl ring-2 ring-blue-400/30\"\n : \"cursor-grab\"\n : \"\"\n }`}\n >\n <img\n src={image.dataUrl}\n alt={image.name}\n className=\"w-full h-full object-cover\"\n draggable={false}\n />\n <span className=\"absolute bottom-1 left-1 right-1 text-[9px] text-white bg-black/50 rounded px-1 py-0.5 truncate opacity-0 group-hover:opacity-100 transition-opacity\">\n {image.name}\n </span>\n </div>\n );\n}\n","import { DesignFrame, DEFAULT_FRAME_WIDTH, PipelineStatusOverlay } from \"@calca/canvas-ui\";\nimport type { Comment as CommentType, DesignIteration, PipelineStatus } from \"@calca/shared\";\nimport { useViewport, type Node, type NodeProps } from \"@xyflow/react\";\n\nexport type DesignFrameNodeData = {\n iteration: DesignIteration;\n groupId: string;\n isCommentMode: boolean;\n isSelectMode: boolean;\n isDragging: boolean;\n pipelineStatus?: PipelineStatus;\n onAddComment: (draft: {\n iterationId: string;\n position: { x: number; y: number };\n screenX: number;\n screenY: number;\n }) => void;\n onClickComment: (comment: CommentType, iterationId: string) => void;\n};\n\nexport type DesignFrameNodeType = Node<DesignFrameNodeData, \"designFrame\">;\n\nexport function DesignFrameNode({ data, selected }: NodeProps<DesignFrameNodeType>) {\n const { iteration, isCommentMode, isSelectMode, pipelineStatus, onAddComment, onClickComment } =\n data;\n const { zoom } = useViewport();\n\n const handleAddComment = (\n iterationId: string,\n position: { x: number; y: number },\n screenX: number,\n screenY: number,\n ) => {\n onAddComment({ iterationId, position, screenX, screenY });\n };\n\n const frameWidth = iteration.width || DEFAULT_FRAME_WIDTH;\n const frameHeight = iteration.isLoading ? 320 : iteration.height || 320;\n\n return (\n <div style={{ width: frameWidth }}>\n <div className=\"mb-2 flex items-center gap-2 group/label pointer-events-none\">\n <span className=\"text-xs font-medium text-gray-500/80 bg-white/60 backdrop-blur-sm px-2.5 py-0.5 rounded-lg border border-white/40\">\n {iteration.label}\n </span>\n </div>\n\n <DesignFrame\n iteration={iteration}\n width={frameWidth}\n isCommentMode={isCommentMode}\n isSelectMode={isSelectMode}\n isDragging={data.isDragging}\n isSelected={selected}\n scale={zoom}\n onAddComment={handleAddComment}\n onClickComment={onClickComment}\n />\n\n {pipelineStatus && pipelineStatus.stage !== \"done\" && (\n <PipelineStatusOverlay\n status={pipelineStatus}\n x={0}\n y={0}\n width={frameWidth}\n frameHeight={frameHeight}\n />\n )}\n </div>\n );\n}\n","import type { Command, ShapeRecord } from \"@calca/canvas-base\";\nimport type {\n CanvasImage,\n Comment,\n GenerationGroup,\n PipelineStatus,\n Point,\n ToolMode,\n} from \"@calca/shared\";\nimport { Background, Panel, SelectionMode, type Node, type NodeChange } from \"@xyflow/react\";\n\nimport \"@xyflow/react/dist/style.css\";\nimport { useCallback, useEffect, useMemo, useRef } from \"react\";\n\nimport { FlowCanvas, useCanvasStore } from \"../adapter\";\nimport type { CanvasHandle } from \"../hooks/use-canvas\";\nimport { calcaViews, imageToRecord, iterationToRecord } from \"../views\";\nimport { CanvasImageNode } from \"./nodes/CanvasImageNode\";\nimport { DesignFrameNode } from \"./nodes/DesignFrameNode\";\n\ntype RubberBand = {\n startX: number;\n startY: number;\n currentX: number;\n currentY: number;\n} | null;\n\ntype Updater<T> = T | ((prev: T) => T);\n\ninterface CanvasAreaProps {\n /** @deprecated CanvasArea now uses React Flow's native viewport; this prop is kept for API compatibility. */\n canvas: CanvasHandle;\n groups: GenerationGroup[];\n onGroupsChange: (update: Updater<GenerationGroup[]>) => void;\n canvasImages: CanvasImage[];\n onCanvasImagesChange: (update: Updater<CanvasImage[]>) => void;\n selectedIds: Set<string>;\n onSelectedIdsChange: (update: Updater<Set<string>>) => void;\n toolMode: ToolMode;\n spaceHeld: boolean;\n /** @deprecated Rubber-band selection is handled by React Flow's built-in selection mode. */\n rubberBand?: RubberBand;\n /** @deprecated Rubber-band selection is handled by React Flow's built-in selection mode. */\n setRubberBand?: (update: Updater<RubberBand>) => void;\n draggingId: string | null;\n setDraggingId: (id: string | null) => void;\n draggingImageId: string | null;\n setDraggingImageId: (id: string | null) => void;\n pipelineStages?: Record<string, PipelineStatus>;\n onAddComment: (draft: {\n iterationId: string;\n position: Point;\n screenX: number;\n screenY: number;\n }) => void;\n onClickComment: (comment: Comment, iterationId: string) => void;\n onImageDrop?: (files: File[], dropX?: number, dropY?: number) => void;\n onContextMenu?: (e: React.MouseEvent) => void;\n emptyTitle?: string;\n emptyDescription?: string;\n toolbar?: React.ReactNode;\n}\n\nconst nodeTypes = {\n canvasImage: CanvasImageNode,\n designFrame: DesignFrameNode,\n};\n\nconst propsEqual = (a: Record<string, unknown>, b: Record<string, unknown>) => {\n const keys = new Set([...Object.keys(a), ...Object.keys(b)]);\n for (const key of keys) {\n if (!Object.is(a[key], b[key])) return false;\n }\n return true;\n};\n\nexport const CanvasArea = ({\n groups,\n onGroupsChange,\n canvasImages,\n onCanvasImagesChange,\n selectedIds,\n onSelectedIdsChange,\n toolMode,\n spaceHeld,\n draggingId: _draggingId,\n setDraggingId,\n draggingImageId: _draggingImageId,\n setDraggingImageId,\n pipelineStages,\n onAddComment,\n onClickComment,\n onImageDrop,\n onContextMenu,\n emptyTitle,\n emptyDescription,\n toolbar,\n}: CanvasAreaProps) => {\n const store = useCanvasStore();\n if (!store) {\n throw new Error(\"CanvasArea must render inside a CanvasStoreProvider\");\n }\n\n const isSelectMode = toolMode === \"select\" && !spaceHeld;\n const isCommentMode = toolMode === \"comment\" && !spaceHeld;\n\n // Bridge the app's controlled props into the document: diff by record id\n // and apply create/update/delete commands with history off (the app owns\n // persistence; the store is the render source of truth).\n const prevRecords = useRef(new Map<string, ShapeRecord>());\n useEffect(() => {\n const next = new Map<string, ShapeRecord>();\n const prev = prevRecords.current;\n const indexOf = (id: string) => prev.get(id)?.index ?? store.nextIndex(null);\n\n for (const group of groups) {\n for (const iteration of group.iterations) {\n next.set(iteration.id, iterationToRecord(iteration, group.id, indexOf(iteration.id)));\n }\n }\n for (const image of canvasImages) {\n next.set(image.id, imageToRecord(image, indexOf(image.id)));\n }\n\n const commands: Command[] = [];\n for (const [id, record] of next) {\n const existing = prev.get(id);\n if (!existing) {\n commands.push({ op: \"create-shape\", record });\n } else if (!propsEqual(existing.props, record.props)) {\n commands.push({ op: \"update\", id, props: record.props });\n }\n }\n for (const id of prev.keys()) {\n if (!next.has(id)) commands.push({ op: \"delete\", id });\n }\n\n prevRecords.current = next;\n if (commands.length > 0) {\n store.apply(commands, { origin: \"remote\", history: false });\n }\n }, [groups, canvasImages, store]);\n\n // Write document changes back into the app's jotai-backed props so\n // persistence and other consumers keep working unchanged.\n const onCommandsApplied = useCallback(\n (commands: Command[], _changes: NodeChange[]) => {\n const positionUpdates = new Map<string, Point>();\n const removedIds = new Set<string>();\n for (const command of commands) {\n if (command.op === \"update\" && typeof command.props.position === \"object\") {\n positionUpdates.set(command.id, command.props.position as Point);\n } else if (command.op === \"delete\") {\n removedIds.add(command.id);\n }\n }\n\n if (positionUpdates.size > 0) {\n onGroupsChange((prevGroups) =>\n prevGroups.map((group) => ({\n ...group,\n iterations: group.iterations.map((iter) => {\n const update = positionUpdates.get(iter.id);\n return update ? { ...iter, position: update } : iter;\n }),\n })),\n );\n onCanvasImagesChange((prevImages) =>\n prevImages.map((img) => {\n const update = positionUpdates.get(img.id);\n return update ? { ...img, position: update } : img;\n }),\n );\n }\n\n if (removedIds.size > 0) {\n onGroupsChange((prevGroups) =>\n prevGroups\n .map((group) => ({\n ...group,\n iterations: group.iterations.filter((iter) => !removedIds.has(iter.id)),\n }))\n .filter((group) => group.iterations.length > 0),\n );\n onCanvasImagesChange((prevImages) => prevImages.filter((img) => !removedIds.has(img.id)));\n }\n },\n [onGroupsChange, onCanvasImagesChange],\n );\n\n const onNodeDragStart = useCallback(\n (_event: MouseEvent | TouchEvent, node: Node) => {\n const isImage = canvasImages.some((img) => img.id === node.id);\n if (isImage) {\n setDraggingImageId(node.id);\n } else {\n setDraggingId(node.id);\n }\n },\n [canvasImages, setDraggingId, setDraggingImageId],\n );\n\n const onNodeDragStop = useCallback(() => {\n setDraggingId(null);\n setDraggingImageId(null);\n }, [setDraggingId, setDraggingImageId]);\n\n const onSelectionChange = useCallback(\n ({ nodes: selectedNodes }: { nodes: Node[] }) => {\n onSelectedIdsChange(new Set(selectedNodes.map((n) => n.id)));\n },\n [onSelectedIdsChange],\n );\n\n const onPaneClick = useCallback(() => {\n onSelectedIdsChange(new Set());\n }, [onSelectedIdsChange]);\n\n const handleDragOver = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n e.dataTransfer.dropEffect = \"copy\";\n }, []);\n\n const handleDrop = useCallback(\n (e: React.DragEvent) => {\n e.preventDefault();\n const files = [...e.dataTransfer.files].filter((f) => f.type.startsWith(\"image/\"));\n if (files.length > 0 && onImageDrop) {\n onImageDrop(files, e.clientX, e.clientY);\n }\n },\n [onImageDrop],\n );\n\n const handleContextMenu = useCallback(\n (e: React.MouseEvent) => {\n if (onContextMenu) {\n e.preventDefault();\n onContextMenu(e);\n }\n },\n [onContextMenu],\n );\n\n const ctx = useMemo(\n () => ({\n isSelectMode,\n isCommentMode,\n selectedIds,\n pipelineStages,\n onAddComment,\n onClickComment,\n }),\n [isSelectMode, isCommentMode, selectedIds, pipelineStages, onAddComment, onClickComment],\n );\n\n return (\n <div\n className=\"absolute inset-0 canvas-dots\"\n onDragOver={handleDragOver}\n onDrop={handleDrop}\n onContextMenu={handleContextMenu}\n >\n <FlowCanvas\n views={calcaViews}\n data={ctx}\n nodeTypes={nodeTypes}\n onCommandsApplied={onCommandsApplied}\n onSelectionChange={onSelectionChange}\n onPaneClick={onPaneClick}\n onNodeDragStart={onNodeDragStart}\n onNodeDragStop={onNodeDragStop}\n fitView\n fitViewOptions={{ padding: 0.2 }}\n minZoom={0.1}\n maxZoom={5}\n panOnScroll\n selectionOnDrag={isSelectMode}\n selectionMode={SelectionMode.Partial}\n panOnDrag={spaceHeld ? [0, 1, 2] : false}\n nodesDraggable={isSelectMode}\n elementsSelectable={isSelectMode}\n selectNodesOnDrag={isSelectMode}\n multiSelectionKeyCode=\"Shift\"\n deleteKeyCode={null}\n proOptions={{ hideAttribution: true }}\n className={isCommentMode ? \"cursor-crosshair\" : \"cursor-default\"}\n >\n <Background gap={20} size={1} color=\"#e5e7eb\" />\n\n {/* 1001: selected nodes elevate to z-index 1000 and must not cover the toolbar */}\n {toolbar && (\n <Panel position=\"top-center\" style={{ zIndex: 1001 }}>\n {toolbar}\n </Panel>\n )}\n </FlowCanvas>\n\n {groups.length === 0 && canvasImages.length === 0 && emptyTitle && (\n <div className=\"absolute inset-0 flex flex-col items-center justify-center pointer-events-none z-10\">\n <div className=\"text-center\">\n <h1 className=\"text-2xl font-semibold text-gray-300 mb-2\">{emptyTitle}</h1>\n {emptyDescription && <p className=\"text-gray-400/70 text-sm\">{emptyDescription}</p>}\n </div>\n </div>\n )}\n\n <style>{`\n .react-flow__node-designFrame,\n .react-flow__node-canvasImage {\n border: none !important;\n background: transparent !important;\n padding: 0 !important;\n overflow: visible !important;\n }\n .react-flow__node-designFrame.selected,\n .react-flow__node-canvasImage.selected {\n box-shadow: none !important;\n }\n .react-flow__node-designFrame .react-flow__handle {\n opacity: 0;\n }\n `}</style>\n </div>\n );\n};\n\nexport type { CanvasAreaProps };\n","import { ReactFlowProvider } from \"@xyflow/react\";\nimport type { ReactNode } from \"react\";\n\nimport { CanvasStoreProvider } from \"../adapter\";\n\ninterface CanvasProviderProps {\n children: ReactNode;\n}\n\nexport function CanvasProvider({ children }: CanvasProviderProps) {\n return (\n <CanvasStoreProvider>\n <ReactFlowProvider>{children}</ReactFlowProvider>\n </CanvasStoreProvider>\n );\n}\n","import type { Point } from \"@calca/shared\";\nimport { atom } from \"jotai\";\n\nexport const canvasOffsetAtom = atom<Point>({ x: 0, y: 0 });\nexport const canvasScaleAtom = atom<number>(1);\nexport const isPanningAtom = atom<boolean>(false);\n","import { useReactFlow, useViewport } from \"@xyflow/react\";\nimport { useSetAtom } from \"jotai\";\nimport { useCallback, useEffect } from \"react\";\n\nimport { canvasOffsetAtom, canvasScaleAtom } from \"../state/canvas-atoms\";\n\ninterface Point {\n x: number;\n y: number;\n}\n\nexport type CanvasHandle = {\n offset: Point;\n scale: number;\n resetView: () => void;\n zoomIn: () => void;\n zoomOut: () => void;\n fitToView: () => void;\n zoomToFit: (bounds: { minX: number; minY: number; maxX: number; maxY: number }) => void;\n screenToCanvas: (screenX: number, screenY: number, rect: DOMRect) => Point;\n};\n\nexport function useCanvasViewport(): CanvasHandle {\n const { x, y, zoom } = useViewport();\n const { setViewport, fitView } = useReactFlow();\n const setCanvasOffset = useSetAtom(canvasOffsetAtom);\n const setCanvasScale = useSetAtom(canvasScaleAtom);\n\n useEffect(() => {\n setCanvasOffset({ x, y });\n setCanvasScale(zoom);\n }, [x, y, zoom, setCanvasOffset, setCanvasScale]);\n\n const resetView = useCallback(() => {\n setViewport({ x: 0, y: 0, zoom: 1 });\n }, [setViewport]);\n\n const zoomIn = useCallback(() => {\n setViewport({ x, y, zoom: Math.min(zoom * 1.2, 5) });\n }, [setViewport, x, y, zoom]);\n\n const zoomOut = useCallback(() => {\n setViewport({ x, y, zoom: Math.max(zoom * 0.8, 0.1) });\n }, [setViewport, x, y, zoom]);\n\n const fitToView = useCallback(() => {\n void fitView({ padding: 0.2, duration: 300 });\n }, [fitView]);\n\n const zoomToFit = useCallback(\n (bounds: { minX: number; minY: number; maxX: number; maxY: number }) => {\n const padding = 80;\n const contentW = bounds.maxX - bounds.minX;\n const contentH = bounds.maxY - bounds.minY;\n if (contentW <= 0 || contentH <= 0) {\n return;\n }\n\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n const scaleX = (vw - padding * 2) / contentW;\n const scaleY = (vh - padding * 2) / contentH;\n const nextZoom = Math.min(scaleX, scaleY, 1);\n\n const centerX = (bounds.minX + bounds.maxX) / 2;\n const centerY = (bounds.minY + bounds.maxY) / 2;\n\n setViewport({\n x: vw / 2 - centerX * nextZoom,\n y: vh / 2 - centerY * nextZoom,\n zoom: nextZoom,\n });\n },\n [setViewport],\n );\n\n const screenToCanvas = useCallback(\n (screenX: number, screenY: number, rect: DOMRect): Point => ({\n x: (screenX - rect.left - x) / zoom,\n y: (screenY - rect.top - y) / zoom,\n }),\n [x, y, zoom],\n );\n\n return {\n offset: { x, y },\n scale: zoom,\n resetView,\n zoomIn,\n zoomOut,\n fitToView,\n zoomToFit,\n screenToCanvas,\n };\n}\n\n/** Backward-compatible alias for code that still imports `useCanvas`. */\nexport const useCanvas = useCanvasViewport;\n","import type { CanvasImage } from \"@calca/shared\";\nimport type { GenerationGroup } from \"@calca/shared\";\nimport type { Point } from \"@calca/shared\";\n\n/** Frames data stored in the internal Jotai clipboard atom */\nexport interface ClipboardFramesData {\n type: \"frames\";\n /** Groups (each group becomes a new group on paste) */\n groups: GenerationGroup[];\n /** Standalone canvas images copied with the frames */\n images: CanvasImage[];\n /** Relative positions (iteration id → {dx, dy} from bounding-box origin) */\n relativePositions: Record<string, Point>;\n /** Bounding-box origin of the original selection */\n origin: Point;\n createdAt: number;\n}\n\n/**\n * Serializes selected iterations + their images into the internal clipboard format.\n * Returns null if no iterations were selected.\n */\nexport function copyFrames(\n selectedIds: Set<string>,\n groups: GenerationGroup[],\n images: CanvasImage[],\n): ClipboardFramesData | null {\n if (selectedIds.size === 0) return null;\n\n const selectedIterationsByGroup = new Map<string, GenerationGroup>();\n\n for (const group of groups) {\n const selectedIters = group.iterations.filter((iter) => selectedIds.has(iter.id));\n if (selectedIters.length > 0) {\n selectedIterationsByGroup.set(group.id, { ...group, iterations: selectedIters });\n }\n }\n\n if (selectedIterationsByGroup.size === 0) return null;\n\n const selectedImages = images.filter((img) => selectedIds.has(img.id));\n\n let minX = Infinity;\n let minY = Infinity;\n for (const [, group] of selectedIterationsByGroup) {\n for (const iter of group.iterations) {\n if (iter.position.x < minX) minX = iter.position.x;\n if (iter.position.y < minY) minY = iter.position.y;\n }\n }\n const origin: Point = { x: minX, y: minY };\n\n const relativePositions: Record<string, Point> = {};\n for (const [, group] of selectedIterationsByGroup) {\n for (const iter of group.iterations) {\n relativePositions[iter.id] = {\n x: iter.position.x - origin.x,\n y: iter.position.y - origin.y,\n };\n }\n }\n\n return {\n type: \"frames\",\n groups: Array.from(selectedIterationsByGroup.values()),\n images: selectedImages,\n relativePositions,\n origin,\n createdAt: Date.now(),\n };\n}\n\n/**\n * Deserializes clipboard data and returns new groups + images positioned at viewportCenter.\n * Assigns fresh unique IDs to all created entities.\n */\nexport function pasteFrames(\n clipboardData: ClipboardFramesData,\n viewportCenter: Point,\n): { groups: GenerationGroup[]; images: CanvasImage[] } {\n const now = Date.now();\n const newGroups: GenerationGroup[] = [];\n const newImages: CanvasImage[] = [];\n\n for (const group of clipboardData.groups) {\n const newGroupId = `${group.id}-paste-${now}`;\n const newIterations = group.iterations.map((iter) => {\n const rel = clipboardData.relativePositions[iter.id] ?? { x: 0, y: 0 };\n return {\n ...iter,\n id: `${iter.id}-paste-${now}`,\n position: {\n x: viewportCenter.x + rel.x,\n y: viewportCenter.y + rel.y,\n },\n };\n });\n\n newGroups.push({\n ...group,\n id: newGroupId,\n iterations: newIterations,\n createdAt: now,\n });\n\n for (const img of clipboardData.images) {\n const rel = clipboardData.relativePositions[img.id] ?? { x: 0, y: 0 };\n newImages.push({\n ...img,\n id: `${img.id}-paste-${now}`,\n position: {\n x: viewportCenter.x + rel.x,\n y: viewportCenter.y + rel.y,\n },\n });\n }\n }\n\n return { groups: newGroups, images: newImages };\n}\n\nexport function cutFrames(\n selectedIds: Set<string>,\n groups: GenerationGroup[],\n images: CanvasImage[],\n): {\n clipboardData: ClipboardFramesData | null;\n groups: GenerationGroup[];\n images: CanvasImage[];\n} {\n const clipboardData = copyFrames(selectedIds, groups, images);\n\n const updatedGroups = groups\n .map((g) => ({\n ...g,\n iterations: g.iterations.filter((iter) => !selectedIds.has(iter.id)),\n }))\n .filter((g) => g.iterations.length > 0);\n\n const updatedImages = images.filter((img) => !selectedIds.has(img.id));\n\n return {\n clipboardData,\n groups: updatedGroups,\n images: updatedImages,\n };\n}\n","import type { GenerationGroup } from \"@calca/shared\";\nimport { atom } from \"jotai\";\n\nconst STORAGE_KEY = \"calca-canvas-session\";\nconst IMG_DB_NAME = \"calca-canvas-images\";\nconst IMG_STORE = \"images\";\n\nfunction openImgDB(): Promise<IDBDatabase> {\n return new Promise((resolve, reject) => {\n const req = indexedDB.open(IMG_DB_NAME, 1);\n req.onupgradeneeded = () => req.result.createObjectStore(IMG_STORE);\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => reject(req.error);\n });\n}\n\nasync function saveImagesToIDB(images: Record<string, string>): Promise<void> {\n const db = await openImgDB();\n const tx = db.transaction(IMG_STORE, \"readwrite\");\n const store = tx.objectStore(IMG_STORE);\n for (const [key, val] of Object.entries(images)) {\n store.put(val, key);\n }\n return new Promise((resolve, reject) => {\n tx.oncomplete = () => {\n db.close();\n resolve();\n };\n tx.onerror = () => {\n db.close();\n reject(tx.error);\n };\n });\n}\n\nasync function loadImagesFromIDB(): Promise<Record<string, string>> {\n const db = await openImgDB();\n const tx = db.transaction(IMG_STORE, \"readonly\");\n const store = tx.objectStore(IMG_STORE);\n const result: Record<string, string> = {};\n return new Promise((resolve, reject) => {\n const cursor = store.openCursor();\n cursor.onsuccess = () => {\n const c = cursor.result;\n if (c) {\n result[c.key as string] = c.value;\n c.continue();\n } else {\n db.close();\n resolve(result);\n }\n };\n cursor.onerror = () => {\n db.close();\n reject(cursor.error);\n };\n });\n}\n\n// Regex `src=\"(data:image/...)\"` → `[idb:key]` placeholder for IndexedDB offloading\nfunction extractBase64(groups: GenerationGroup[]): {\n stripped: GenerationGroup[];\n images: Record<string, string>;\n} {\n const images: Record<string, string> = {};\n let counter = 0;\n const stripped = groups.map((g) => ({\n ...g,\n iterations: g.iterations.map((it) => ({\n ...it,\n html: it.html\n ? it.html.replace(/src=\"(data:image\\/[^\"]+)\"/g, (_m, uri) => {\n const key = `img_${g.id}_${it.id}_${counter++}`;\n images[key] = uri;\n return `src=\"[idb:${key}]\"`;\n })\n : it.html,\n })),\n }));\n return { images, stripped };\n}\n\nfunction restoreBase64(\n groups: GenerationGroup[],\n images: Record<string, string>,\n): GenerationGroup[] {\n return groups.map((g) => ({\n ...g,\n iterations: g.iterations.map((it) => ({\n ...it,\n html: it.html\n ? it.html.replace(/src=\"\\[idb:([^\\]]+)\\]\"/g, (_m, key) =>\n images[key] ? `src=\"${images[key]}\"` : 'src=\"[img-missing]\"',\n )\n : it.html,\n })),\n }));\n}\n\nlet saveTimer: ReturnType<typeof setTimeout> | null = null;\n\nfunction debouncedPersist(groups: GenerationGroup[]): void {\n if (saveTimer) {\n clearTimeout(saveTimer);\n }\n saveTimer = setTimeout(() => {\n try {\n const toSave = groups.filter((g) => g.iterations.some((it) => it.html && !it.isLoading));\n const { stripped, images } = extractBase64(toSave);\n localStorage.setItem(STORAGE_KEY, JSON.stringify(stripped));\n if (Object.keys(images).length > 0) {\n saveImagesToIDB(images).catch((error) =>\n console.debug(\"Failed to save images to IndexedDB\", {\n error: error instanceof Error ? error.message : String(error),\n }),\n );\n }\n } catch (error) {\n console.debug(\"Failed to save canvas session\", {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }, 500);\n}\n\nconst _groupsBase = atom<GenerationGroup[]>([]);\n\nexport const groupsAtom = atom(\n (get: (a: typeof _groupsBase) => GenerationGroup[]) => get(_groupsBase),\n (\n _get: unknown,\n set: (\n a: typeof _groupsBase,\n v: GenerationGroup[] | ((p: GenerationGroup[]) => GenerationGroup[]),\n ) => void,\n update: GenerationGroup[] | ((prev: GenerationGroup[]) => GenerationGroup[]),\n ) => {\n set(_groupsBase, (prev) => {\n const next = typeof update === \"function\" ? update(prev) : update;\n debouncedPersist(next);\n return next;\n });\n },\n);\n\nexport const resetSessionAtom = atom(\n null,\n (_get: unknown, set: (a: typeof groupsAtom, v: GenerationGroup[]) => void) => {\n set(groupsAtom, []);\n try {\n localStorage.removeItem(STORAGE_KEY);\n } catch {}\n },\n);\n\nexport async function hydrateGroups(\n setGroups: (g: GenerationGroup[] | ((p: GenerationGroup[]) => GenerationGroup[])) => void,\n): Promise<void> {\n try {\n const raw = localStorage.getItem(STORAGE_KEY);\n if (raw) {\n const parsed = JSON.parse(raw) as GenerationGroup[];\n const valid = parsed.filter((g) => g.iterations.some((it) => it.html && !it.isLoading));\n if (valid.length > 0) {\n // Apply only if nothing was written while the async reads were in flight.\n try {\n const images = await loadImagesFromIDB();\n const restored = restoreBase64(valid, images);\n setGroups((prev) => (prev.length === 0 ? restored : prev));\n } catch {\n setGroups((prev) => (prev.length === 0 ? valid : prev));\n }\n return;\n }\n }\n } catch {}\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,52 @@
1
1
  {
2
2
  "name": "@calca/canvas-flow",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.7.3",
4
+ "private": false,
5
+ "license": "Apache-2.0",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "type": "module",
10
+ "sideEffects": false,
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.mts",
14
+ "import": "./dist/index.mjs"
15
+ }
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "scripts": {
21
+ "build": "tsdown",
22
+ "typecheck": "bunx tsc --noEmit",
23
+ "lint": "oxlint --fix .",
24
+ "format": "oxfmt --write .",
25
+ "test": "vitest run --passWithNoTests",
26
+ "clean": "rm -rf dist",
27
+ "validate": "bun ../../scripts/validate.ts --cwd $INIT_CWD",
28
+ "check:pack": "publint && attw --pack . --profile esm-only"
29
+ },
30
+ "dependencies": {
31
+ "@calca/canvas-base": "0.7.3",
32
+ "@calca/canvas-ui": "0.7.3",
33
+ "@calca/shared": "0.7.3",
34
+ "@xyflow/react": "^12.11.1",
35
+ "jotai": "^2.19.1"
36
+ },
37
+ "devDependencies": {
38
+ "@arethetypeswrong/cli": "^0.18.5",
39
+ "@types/react": "^19.2.14",
40
+ "@types/react-dom": "^19.2.3",
41
+ "publint": "^0.3.25",
42
+ "react": "^19.2.4",
43
+ "react-dom": "^19.2.4",
44
+ "tsdown": "^0.23.0",
45
+ "typescript": "^6.0.3",
46
+ "vitest": "^4.1.5"
47
+ },
48
+ "peerDependencies": {
49
+ "react": "^19.2.4",
50
+ "react-dom": "^19.2.4"
51
+ }
52
+ }