@sqlrooms/canvas 0.28.1-rc.0 → 0.29.0-rc.0
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 +5 -95
- package/dist/Canvas.d.ts.map +1 -1
- package/dist/Canvas.js +72 -14
- package/dist/Canvas.js.map +1 -1
- package/dist/CanvasSlice.d.ts +42 -93
- package/dist/CanvasSlice.d.ts.map +1 -1
- package/dist/CanvasSlice.js +183 -305
- package/dist/CanvasSlice.js.map +1 -1
- package/dist/crdt.d.ts +5 -16
- package/dist/crdt.d.ts.map +1 -1
- package/dist/crdt.js +41 -28
- package/dist/crdt.js.map +1 -1
- package/dist/edgeUtils.d.ts +3 -0
- package/dist/edgeUtils.d.ts.map +1 -0
- package/dist/edgeUtils.js +7 -0
- package/dist/edgeUtils.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/nodes/AddNodePopover.d.ts.map +1 -1
- package/dist/nodes/AddNodePopover.js +16 -9
- package/dist/nodes/AddNodePopover.js.map +1 -1
- package/dist/nodes/CanvasNodeContainer.d.ts.map +1 -1
- package/dist/nodes/CanvasNodeContainer.js +5 -9
- package/dist/nodes/CanvasNodeContainer.js.map +1 -1
- package/package.json +8 -10
- package/dist/CanvasAssistantDrawer.d.ts +0 -3
- package/dist/CanvasAssistantDrawer.d.ts.map +0 -1
- package/dist/CanvasAssistantDrawer.js +0 -14
- package/dist/CanvasAssistantDrawer.js.map +0 -1
- package/dist/dag.d.ts +0 -53
- package/dist/dag.d.ts.map +0 -1
- package/dist/dag.js +0 -124
- package/dist/dag.js.map +0 -1
- package/dist/nodes/SqlNode.d.ts +0 -11
- package/dist/nodes/SqlNode.d.ts.map +0 -1
- package/dist/nodes/SqlNode.js +0 -52
- package/dist/nodes/SqlNode.js.map +0 -1
- package/dist/nodes/VegaNode.d.ts +0 -11
- package/dist/nodes/VegaNode.d.ts.map +0 -1
- package/dist/nodes/VegaNode.js +0 -12
- package/dist/nodes/VegaNode.js.map +0 -1
package/README.md
CHANGED
|
@@ -2,101 +2,11 @@ React Flow-based canvas for building SQL + Vega node DAGs in SQLRooms apps.
|
|
|
2
2
|
|
|
3
3
|
This package includes:
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- `Canvas` component for graph editing/execution
|
|
7
|
-
- `CanvasSliceConfig` Zod schema for persistence
|
|
5
|
+
Refer to the [Canvas example](https://github.com/sqlrooms/examples/tree/main/canvas).
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
## Stable vs internal imports
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
Use root imports from `@sqlrooms/canvas` as the stable API surface.
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## Quick start
|
|
18
|
-
|
|
19
|
-
```tsx
|
|
20
|
-
import {Canvas, CanvasSliceState, createCanvasSlice} from '@sqlrooms/canvas';
|
|
21
|
-
import {
|
|
22
|
-
createRoomShellSlice,
|
|
23
|
-
createRoomStore,
|
|
24
|
-
LayoutTypes,
|
|
25
|
-
RoomShellSliceState,
|
|
26
|
-
} from '@sqlrooms/room-shell';
|
|
27
|
-
|
|
28
|
-
type RoomState = RoomShellSliceState & CanvasSliceState;
|
|
29
|
-
|
|
30
|
-
export const {roomStore, useRoomStore} = createRoomStore<RoomState>(
|
|
31
|
-
(set, get, store) => ({
|
|
32
|
-
...createRoomShellSlice({
|
|
33
|
-
config: {
|
|
34
|
-
dataSources: [
|
|
35
|
-
{
|
|
36
|
-
type: 'url',
|
|
37
|
-
tableName: 'earthquakes',
|
|
38
|
-
url: 'https://huggingface.co/datasets/sqlrooms/earthquakes/resolve/main/earthquakes.parquet',
|
|
39
|
-
},
|
|
40
|
-
],
|
|
41
|
-
},
|
|
42
|
-
layout: {
|
|
43
|
-
config: {
|
|
44
|
-
type: LayoutTypes.enum.mosaic,
|
|
45
|
-
nodes: 'main',
|
|
46
|
-
},
|
|
47
|
-
panels: {
|
|
48
|
-
main: {
|
|
49
|
-
title: 'Canvas',
|
|
50
|
-
icon: () => null,
|
|
51
|
-
component: Canvas,
|
|
52
|
-
placement: 'main',
|
|
53
|
-
},
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
})(set, get, store),
|
|
57
|
-
...createCanvasSlice({
|
|
58
|
-
ai: {
|
|
59
|
-
getApiKey: () => '',
|
|
60
|
-
// Keep this aligned with your app's recommended low-latency model.
|
|
61
|
-
defaultModel: 'gpt-5.2-mini',
|
|
62
|
-
},
|
|
63
|
-
})(set, get, store),
|
|
64
|
-
}),
|
|
65
|
-
);
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## Programmatic canvas actions
|
|
69
|
-
|
|
70
|
-
```tsx
|
|
71
|
-
import {Button} from '@sqlrooms/ui';
|
|
72
|
-
|
|
73
|
-
function CanvasActions() {
|
|
74
|
-
const addNode = useRoomStore((state) => state.canvas.addNode);
|
|
75
|
-
const executeSqlNodeQuery = useRoomStore(
|
|
76
|
-
(state) => state.canvas.executeSqlNodeQuery,
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
const addAndRun = async () => {
|
|
80
|
-
const nodeId = addNode({nodeType: 'sql'});
|
|
81
|
-
await executeSqlNodeQuery(nodeId);
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
return <Button onClick={() => void addAndRun()}>Add SQL node</Button>;
|
|
85
|
-
}
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
## Persistence
|
|
89
|
-
|
|
90
|
-
Use `CanvasSliceConfig` with `persistSliceConfigs`:
|
|
91
|
-
|
|
92
|
-
```ts
|
|
93
|
-
import {CanvasSliceConfig} from '@sqlrooms/canvas';
|
|
94
|
-
|
|
95
|
-
sliceConfigSchemas: {
|
|
96
|
-
canvas: CanvasSliceConfig,
|
|
97
|
-
}
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
## Example
|
|
101
|
-
|
|
102
|
-
- Canvas example app: https://github.com/sqlrooms/examples/tree/main/canvas
|
|
11
|
+
- stable: `createCanvasSlice`, `createDefaultCanvasConfig`, `Canvas`, `CanvasSliceConfig`, `CanvasNodeMeta`, `CanvasSheetMeta`
|
|
12
|
+
- internal: direct imports from implementation files under `src/` are not semver-stable and may change without notice
|
package/dist/Canvas.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Canvas.d.ts","sourceRoot":"","sources":["../src/Canvas.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Canvas.d.ts","sourceRoot":"","sources":["../src/Canvas.tsx"],"names":[],"mappings":"AAWA,OAAO,8BAA8B,CAAC;AAEtC,OAAO,KAA+C,MAAM,OAAO,CAAC;AAiCpE,eAAO,MAAM,MAAM,EAAE,KAAK,CAAC,EAuH1B,CAAC"}
|
package/dist/Canvas.js
CHANGED
|
@@ -1,28 +1,86 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Button, useTheme } from '@sqlrooms/ui';
|
|
3
3
|
import { Background, BackgroundVariant, Controls, MiniMap, ReactFlow, } from '@xyflow/react';
|
|
4
4
|
import '@xyflow/react/dist/style.css';
|
|
5
5
|
import { PlusIcon } from 'lucide-react';
|
|
6
|
-
import {
|
|
6
|
+
import { useCallback, useMemo, useRef, useState } from 'react';
|
|
7
7
|
import { useStoreWithCanvas } from './CanvasSlice';
|
|
8
|
+
import { getRenderableEdges } from './edgeUtils';
|
|
8
9
|
import { AddNodePopover } from './nodes/AddNodePopover';
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
import { CanvasNodeContainer } from './nodes/CanvasNodeContainer';
|
|
11
|
+
const RegistryNodeRenderer = ({ id, renderCell }) => {
|
|
12
|
+
const cell = useStoreWithCanvas((s) => s.cells.config.data[id]);
|
|
13
|
+
if (!cell)
|
|
14
|
+
return null;
|
|
15
|
+
return renderCell({
|
|
16
|
+
id,
|
|
17
|
+
cell,
|
|
18
|
+
renderContainer: ({ header, content, footer }) => (_jsxs(CanvasNodeContainer, { id: id, headerRight: header, children: [content, footer] })),
|
|
19
|
+
});
|
|
14
20
|
};
|
|
15
21
|
export const Canvas = () => {
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
|
|
22
|
+
const registry = useStoreWithCanvas((s) => s.cells.cellRegistry);
|
|
23
|
+
const nodeTypes = useMemo(() => {
|
|
24
|
+
return Object.fromEntries(Object.entries(registry).map(([type, reg]) => [
|
|
25
|
+
type,
|
|
26
|
+
({ id }) => (_jsx(RegistryNodeRenderer, { id: id, renderCell: reg.renderCell })),
|
|
27
|
+
]));
|
|
28
|
+
}, [registry]);
|
|
29
|
+
const currentSheetId = useStoreWithCanvas((s) => s.cells.config.currentSheetId);
|
|
30
|
+
const addSheet = useStoreWithCanvas((s) => s.cells.addSheet);
|
|
31
|
+
const canvasSheet = useStoreWithCanvas((s) => {
|
|
32
|
+
const sheetId = currentSheetId ?? s.cells.config.sheetOrder[0];
|
|
33
|
+
return sheetId ? s.canvas.config.sheets[sheetId] : undefined;
|
|
34
|
+
});
|
|
35
|
+
const cellsSheet = useStoreWithCanvas((s) => {
|
|
36
|
+
const sheetId = currentSheetId ?? s.cells.config.sheetOrder[0];
|
|
37
|
+
return sheetId ? s.cells.config.sheets[sheetId] : undefined;
|
|
38
|
+
});
|
|
39
|
+
const cellsData = useStoreWithCanvas((s) => s.cells.config.data);
|
|
40
|
+
const nodes = useMemo(() => {
|
|
41
|
+
if (!cellsSheet)
|
|
42
|
+
return [];
|
|
43
|
+
// Use all cells from the canonical sheet
|
|
44
|
+
const list = cellsSheet.cellIds
|
|
45
|
+
.map((id) => {
|
|
46
|
+
// Get view-specific metadata if it exists
|
|
47
|
+
const canvasNode = canvasSheet?.nodes[id];
|
|
48
|
+
const cell = cellsData[id];
|
|
49
|
+
if (!cell)
|
|
50
|
+
return null;
|
|
51
|
+
return {
|
|
52
|
+
id,
|
|
53
|
+
position: canvasNode?.position ?? { x: 100, y: 100 },
|
|
54
|
+
width: canvasNode?.width ?? 800,
|
|
55
|
+
height: canvasNode?.height ?? 600,
|
|
56
|
+
type: cell.type,
|
|
57
|
+
data: cell.data,
|
|
58
|
+
};
|
|
59
|
+
})
|
|
60
|
+
.filter(Boolean);
|
|
61
|
+
return list;
|
|
62
|
+
}, [canvasSheet, cellsSheet, cellsData]);
|
|
63
|
+
const edges = useMemo(() => getRenderableEdges(cellsSheet), [cellsSheet]);
|
|
64
|
+
const viewport = canvasSheet?.meta.viewport ?? { x: 0, y: 0, zoom: 1 };
|
|
19
65
|
const applyNodeChanges = useStoreWithCanvas((s) => s.canvas.applyNodeChanges);
|
|
20
|
-
const applyEdgeChanges = useStoreWithCanvas((s) => s.canvas.applyEdgeChanges);
|
|
21
|
-
const viewport = useStoreWithCanvas((s) => s.canvas.config.viewport);
|
|
22
66
|
const setViewport = useStoreWithCanvas((s) => s.canvas.setViewport);
|
|
23
|
-
const
|
|
67
|
+
const [internalViewport, setInternalViewport] = useState(viewport);
|
|
68
|
+
// Debounce viewport updates to prevent rapid state saves from React Flow
|
|
69
|
+
const viewportTimeoutRef = useRef(null);
|
|
70
|
+
const debouncedSetViewport = useCallback((viewport) => {
|
|
71
|
+
setInternalViewport(viewport);
|
|
72
|
+
if (viewportTimeoutRef.current) {
|
|
73
|
+
clearTimeout(viewportTimeoutRef.current);
|
|
74
|
+
}
|
|
75
|
+
viewportTimeoutRef.current = setTimeout(() => {
|
|
76
|
+
setViewport(viewport);
|
|
77
|
+
}, 150);
|
|
78
|
+
}, [setViewport]);
|
|
24
79
|
const empty = nodes.length === 0;
|
|
25
80
|
const { theme: colorMode } = useTheme();
|
|
26
|
-
|
|
81
|
+
// if (!cellsSheet || cellsSheet.type !== 'canvas') {
|
|
82
|
+
// return null;
|
|
83
|
+
// }
|
|
84
|
+
return (_jsx("div", { className: "flex h-full w-full flex-col", children: _jsxs("div", { className: "relative flex-1 overflow-hidden", children: [empty && (_jsx("div", { className: "absolute inset-0 z-10 flex items-center justify-center", children: _jsx(AddNodePopover, { children: _jsxs(Button, { size: "xs", children: [_jsx(PlusIcon, { className: "h-4 w-4" }), "Add node"] }) }) })), _jsxs(ReactFlow, { minZoom: 0.1, colorMode: colorMode, nodes: nodes, edges: edges, nodeTypes: nodeTypes, onNodesChange: applyNodeChanges, viewport: internalViewport, onViewportChange: debouncedSetViewport, nodesConnectable: false, edgesReconnectable: false, connectOnClick: false, children: [_jsx(MiniMap, {}), _jsx(Controls, { position: "top-left" }), _jsx(Background, { variant: BackgroundVariant.Dots, gap: 16, size: 1 })] })] }) }));
|
|
27
85
|
};
|
|
28
86
|
//# sourceMappingURL=Canvas.js.map
|
package/dist/Canvas.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Canvas.js","sourceRoot":"","sources":["../src/Canvas.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAC,MAAM,EAAE,QAAQ,EAAC,MAAM,cAAc,CAAC;AAC9C,OAAO,EACL,UAAU,EACV,iBAAiB,EACjB,QAAQ,EAER,OAAO,EAEP,SAAS,
|
|
1
|
+
{"version":3,"file":"Canvas.js","sourceRoot":"","sources":["../src/Canvas.tsx"],"names":[],"mappings":"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","sourcesContent":["import {Button, useTheme} from '@sqlrooms/ui';\nimport {\n Background,\n BackgroundVariant,\n Controls,\n Edge,\n MiniMap,\n Node,\n ReactFlow,\n Viewport,\n} from '@xyflow/react';\nimport '@xyflow/react/dist/style.css';\nimport {PlusIcon} from 'lucide-react';\nimport React, {useCallback, useMemo, useRef, useState} from 'react';\nimport {useStoreWithCanvas} from './CanvasSlice';\nimport {getRenderableEdges} from './edgeUtils';\nimport {AddNodePopover} from './nodes/AddNodePopover';\nimport {CanvasNodeContainer} from './nodes/CanvasNodeContainer';\n\nconst RegistryNodeRenderer: React.FC<{\n id: string;\n renderCell: (args: {\n id: string;\n cell: import('@sqlrooms/cells').Cell;\n renderContainer: (props: {\n header?: React.ReactNode;\n content: React.ReactNode;\n footer?: React.ReactNode;\n }) => React.ReactElement;\n }) => React.ReactElement;\n}> = ({id, renderCell}) => {\n const cell = useStoreWithCanvas((s) => s.cells.config.data[id]);\n if (!cell) return null;\n\n return renderCell({\n id,\n cell,\n renderContainer: ({header, content, footer}) => (\n <CanvasNodeContainer id={id} headerRight={header}>\n {content}\n {footer}\n </CanvasNodeContainer>\n ),\n });\n};\n\nexport const Canvas: React.FC = () => {\n const registry = useStoreWithCanvas((s) => s.cells.cellRegistry);\n const nodeTypes = useMemo(() => {\n return Object.fromEntries(\n Object.entries(registry).map(([type, reg]) => [\n type,\n ({id}: {id: string}) => (\n <RegistryNodeRenderer id={id} renderCell={reg.renderCell} />\n ),\n ]),\n );\n }, [registry]);\n\n const currentSheetId = useStoreWithCanvas(\n (s) => s.cells.config.currentSheetId,\n );\n const addSheet = useStoreWithCanvas((s) => s.cells.addSheet);\n\n const canvasSheet = useStoreWithCanvas((s) => {\n const sheetId = currentSheetId ?? s.cells.config.sheetOrder[0];\n return sheetId ? s.canvas.config.sheets[sheetId] : undefined;\n });\n\n const cellsSheet = useStoreWithCanvas((s) => {\n const sheetId = currentSheetId ?? s.cells.config.sheetOrder[0];\n return sheetId ? s.cells.config.sheets[sheetId] : undefined;\n });\n\n const cellsData = useStoreWithCanvas((s) => s.cells.config.data);\n\n const nodes = useMemo(() => {\n if (!cellsSheet) return [] as Node[];\n\n // Use all cells from the canonical sheet\n const list = cellsSheet.cellIds\n .map((id: string) => {\n // Get view-specific metadata if it exists\n const canvasNode = canvasSheet?.nodes[id];\n const cell = cellsData[id];\n if (!cell) return null;\n\n return {\n id,\n position: canvasNode?.position ?? {x: 100, y: 100},\n width: canvasNode?.width ?? 800,\n height: canvasNode?.height ?? 600,\n type: cell.type,\n data: cell.data,\n };\n })\n .filter(Boolean);\n return list as unknown as Node[];\n }, [canvasSheet, cellsSheet, cellsData]);\n\n const edges = useMemo(\n () => getRenderableEdges(cellsSheet) as Edge[],\n [cellsSheet],\n );\n const viewport = canvasSheet?.meta.viewport ?? {x: 0, y: 0, zoom: 1};\n const applyNodeChanges = useStoreWithCanvas((s) => s.canvas.applyNodeChanges);\n const setViewport = useStoreWithCanvas((s) => s.canvas.setViewport);\n const [internalViewport, setInternalViewport] = useState<Viewport>(viewport);\n\n // Debounce viewport updates to prevent rapid state saves from React Flow\n const viewportTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const debouncedSetViewport = useCallback(\n (viewport: Viewport) => {\n setInternalViewport(viewport);\n if (viewportTimeoutRef.current) {\n clearTimeout(viewportTimeoutRef.current);\n }\n viewportTimeoutRef.current = setTimeout(() => {\n setViewport(viewport);\n }, 150);\n },\n [setViewport],\n );\n\n const empty = nodes.length === 0;\n const {theme: colorMode} = useTheme();\n\n // if (!cellsSheet || cellsSheet.type !== 'canvas') {\n // return null;\n // }\n\n return (\n <div className=\"flex h-full w-full flex-col\">\n <div className=\"relative flex-1 overflow-hidden\">\n {empty && (\n <div className=\"absolute inset-0 z-10 flex items-center justify-center\">\n <AddNodePopover>\n <Button size=\"xs\">\n <PlusIcon className=\"h-4 w-4\" />\n Add node\n </Button>\n </AddNodePopover>\n </div>\n )}\n <ReactFlow\n minZoom={0.1}\n colorMode={colorMode}\n nodes={nodes as any}\n edges={edges}\n nodeTypes={nodeTypes}\n onNodesChange={applyNodeChanges}\n viewport={internalViewport}\n onViewportChange={debouncedSetViewport}\n nodesConnectable={false}\n edgesReconnectable={false}\n connectOnClick={false}\n // fitView\n >\n <MiniMap />\n <Controls position=\"top-left\" />\n <Background variant={BackgroundVariant.Dots} gap={16} size={1} />\n </ReactFlow>\n </div>\n </div>\n );\n};\n"]}
|
package/dist/CanvasSlice.d.ts
CHANGED
|
@@ -1,122 +1,72 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type Cell, type CellsRootState } from '@sqlrooms/cells';
|
|
2
2
|
import { DuckDbSliceState } from '@sqlrooms/duckdb';
|
|
3
3
|
import type { Viewport, XYPosition } from '@xyflow/react';
|
|
4
4
|
import { Connection, type EdgeChange, type NodeChange } from '@xyflow/react';
|
|
5
5
|
import { z } from 'zod';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
vega: "vega";
|
|
9
|
-
}>;
|
|
10
|
-
export type CanvasNodeTypes = z.infer<typeof CanvasNodeTypes>;
|
|
11
|
-
export declare const CanvasNodeData: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
12
|
-
title: z.ZodDefault<z.ZodString>;
|
|
13
|
-
type: z.ZodLiteral<"sql">;
|
|
14
|
-
sql: z.ZodOptional<z.ZodString>;
|
|
15
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
16
|
-
title: z.ZodDefault<z.ZodString>;
|
|
17
|
-
type: z.ZodLiteral<"vega">;
|
|
18
|
-
sql: z.ZodOptional<z.ZodString>;
|
|
19
|
-
vegaSpec: z.ZodOptional<z.ZodAny>;
|
|
20
|
-
}, z.core.$strip>], "type">;
|
|
21
|
-
export type CanvasNodeData = z.infer<typeof CanvasNodeData>;
|
|
22
|
-
export declare const CanvasNodeSchema: z.ZodObject<{
|
|
6
|
+
/** View metadata for a single node on the canvas */
|
|
7
|
+
export declare const CanvasNodeMeta: z.ZodObject<{
|
|
23
8
|
id: z.ZodString;
|
|
24
9
|
position: z.ZodObject<{
|
|
25
10
|
x: z.ZodNumber;
|
|
26
11
|
y: z.ZodNumber;
|
|
27
12
|
}, z.core.$strip>;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}>;
|
|
32
|
-
data: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
33
|
-
title: z.ZodDefault<z.ZodString>;
|
|
34
|
-
type: z.ZodLiteral<"sql">;
|
|
35
|
-
sql: z.ZodOptional<z.ZodString>;
|
|
36
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
37
|
-
title: z.ZodDefault<z.ZodString>;
|
|
38
|
-
type: z.ZodLiteral<"vega">;
|
|
39
|
-
sql: z.ZodOptional<z.ZodString>;
|
|
40
|
-
vegaSpec: z.ZodOptional<z.ZodAny>;
|
|
41
|
-
}, z.core.$strip>], "type">;
|
|
42
|
-
width: z.ZodNumber;
|
|
43
|
-
height: z.ZodNumber;
|
|
13
|
+
width: z.ZodDefault<z.ZodNumber>;
|
|
14
|
+
height: z.ZodDefault<z.ZodNumber>;
|
|
15
|
+
data: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodAny>>;
|
|
44
16
|
}, z.core.$strip>;
|
|
45
|
-
export type
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
source: z.ZodString;
|
|
49
|
-
target: z.ZodString;
|
|
50
|
-
}, z.core.$strip>;
|
|
51
|
-
export type CanvasEdgeSchema = z.infer<typeof CanvasEdgeSchema>;
|
|
52
|
-
export type SqlNodeQueryResult = {
|
|
53
|
-
status: 'idle';
|
|
54
|
-
} | {
|
|
55
|
-
status: 'loading';
|
|
56
|
-
} | {
|
|
57
|
-
status: 'error';
|
|
58
|
-
error: string;
|
|
59
|
-
} | {
|
|
60
|
-
status: 'success';
|
|
61
|
-
tableName: string;
|
|
62
|
-
lastQueryStatement: string;
|
|
63
|
-
};
|
|
64
|
-
export declare const CanvasSliceConfig: z.ZodObject<{
|
|
17
|
+
export type CanvasNodeMeta = z.infer<typeof CanvasNodeMeta>;
|
|
18
|
+
/** View metadata for a sheet (canvas view) */
|
|
19
|
+
export declare const CanvasSheetMeta: z.ZodObject<{
|
|
65
20
|
viewport: z.ZodObject<{
|
|
66
21
|
x: z.ZodNumber;
|
|
67
22
|
y: z.ZodNumber;
|
|
68
23
|
zoom: z.ZodNumber;
|
|
69
24
|
}, z.core.$strip>;
|
|
70
|
-
|
|
25
|
+
nodeOrder: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
26
|
+
}, z.core.$strip>;
|
|
27
|
+
export type CanvasSheetMeta = z.infer<typeof CanvasSheetMeta>;
|
|
28
|
+
export declare const CanvasSliceConfig: z.ZodObject<{
|
|
29
|
+
sheets: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
71
30
|
id: z.ZodString;
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
31
|
+
nodes: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
32
|
+
id: z.ZodString;
|
|
33
|
+
position: z.ZodObject<{
|
|
34
|
+
x: z.ZodNumber;
|
|
35
|
+
y: z.ZodNumber;
|
|
36
|
+
}, z.core.$strip>;
|
|
37
|
+
width: z.ZodDefault<z.ZodNumber>;
|
|
38
|
+
height: z.ZodDefault<z.ZodNumber>;
|
|
39
|
+
data: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodAny>>;
|
|
40
|
+
}, z.core.$strip>>>;
|
|
41
|
+
meta: z.ZodObject<{
|
|
42
|
+
viewport: z.ZodObject<{
|
|
43
|
+
x: z.ZodNumber;
|
|
44
|
+
y: z.ZodNumber;
|
|
45
|
+
zoom: z.ZodNumber;
|
|
46
|
+
}, z.core.$strip>;
|
|
47
|
+
nodeOrder: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
75
48
|
}, z.core.$strip>;
|
|
76
|
-
type: z.ZodEnum<{
|
|
77
|
-
sql: "sql";
|
|
78
|
-
vega: "vega";
|
|
79
|
-
}>;
|
|
80
|
-
data: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
81
|
-
title: z.ZodDefault<z.ZodString>;
|
|
82
|
-
type: z.ZodLiteral<"sql">;
|
|
83
|
-
sql: z.ZodOptional<z.ZodString>;
|
|
84
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
85
|
-
title: z.ZodDefault<z.ZodString>;
|
|
86
|
-
type: z.ZodLiteral<"vega">;
|
|
87
|
-
sql: z.ZodOptional<z.ZodString>;
|
|
88
|
-
vegaSpec: z.ZodOptional<z.ZodAny>;
|
|
89
|
-
}, z.core.$strip>], "type">;
|
|
90
|
-
width: z.ZodNumber;
|
|
91
|
-
height: z.ZodNumber;
|
|
92
|
-
}, z.core.$strip>>>;
|
|
93
|
-
edges: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
94
|
-
id: z.ZodString;
|
|
95
|
-
source: z.ZodString;
|
|
96
|
-
target: z.ZodString;
|
|
97
49
|
}, z.core.$strip>>>;
|
|
98
50
|
}, z.core.$strip>;
|
|
99
51
|
export type CanvasSliceConfig = z.infer<typeof CanvasSliceConfig>;
|
|
100
|
-
export type CanvasSliceState =
|
|
52
|
+
export type CanvasSliceState = {
|
|
101
53
|
canvas: {
|
|
102
54
|
config: CanvasSliceConfig;
|
|
103
|
-
isAssistantOpen: boolean;
|
|
104
|
-
sqlResults: Record<string, SqlNodeQueryResult>;
|
|
105
55
|
initialize: () => Promise<void>;
|
|
106
56
|
setConfig: (config: CanvasSliceConfig) => void;
|
|
107
57
|
setViewport: (viewport: Viewport) => void;
|
|
108
|
-
|
|
58
|
+
getCanvasSheets: () => Record<string, import('@sqlrooms/cells').Sheet>;
|
|
109
59
|
addNode: (params: {
|
|
110
|
-
|
|
111
|
-
nodeType?:
|
|
60
|
+
sheetId: string;
|
|
61
|
+
nodeType?: string;
|
|
112
62
|
initialPosition?: XYPosition;
|
|
113
|
-
|
|
114
|
-
|
|
63
|
+
parentId?: string;
|
|
64
|
+
}) => Promise<string>;
|
|
115
65
|
renameNode: (nodeId: string, newTitle: string) => Promise<void>;
|
|
116
|
-
updateNode: (nodeId: string, updater: (
|
|
66
|
+
updateNode: (nodeId: string, updater: (cell: Cell) => Cell) => Promise<void>;
|
|
117
67
|
deleteNode: (nodeId: string) => void;
|
|
118
|
-
applyNodeChanges: (changes: NodeChange<
|
|
119
|
-
applyEdgeChanges: (changes: EdgeChange<
|
|
68
|
+
applyNodeChanges: (changes: NodeChange<CanvasNodeMeta>[]) => void;
|
|
69
|
+
applyEdgeChanges: (changes: EdgeChange<any>[]) => void;
|
|
120
70
|
addEdge: (edge: Connection) => void;
|
|
121
71
|
executeSqlNodeQuery: (nodeId: string, opts?: {
|
|
122
72
|
cascade?: boolean;
|
|
@@ -124,10 +74,9 @@ export type CanvasSliceState = AiSliceState & {
|
|
|
124
74
|
};
|
|
125
75
|
};
|
|
126
76
|
export declare function createDefaultCanvasConfig(props?: Partial<CanvasSliceConfig>): CanvasSliceConfig;
|
|
127
|
-
export declare function createCanvasSlice(props
|
|
77
|
+
export declare function createCanvasSlice(props?: {
|
|
128
78
|
config?: Partial<CanvasSliceConfig>;
|
|
129
|
-
ai?: Partial<Parameters<typeof createAiSlice>[0]>;
|
|
130
79
|
}): import("zustand").StateCreator<CanvasSliceState>;
|
|
131
|
-
export type DuckDbSliceStateWithCanvas = DuckDbSliceState & CanvasSliceState;
|
|
80
|
+
export type DuckDbSliceStateWithCanvas = DuckDbSliceState & CanvasSliceState & CellsRootState;
|
|
132
81
|
export declare function useStoreWithCanvas<T>(selector: (state: DuckDbSliceStateWithCanvas) => T): T;
|
|
133
82
|
//# sourceMappingURL=CanvasSlice.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CanvasSlice.d.ts","sourceRoot":"","sources":["../src/CanvasSlice.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"CanvasSlice.d.ts","sourceRoot":"","sources":["../src/CanvasSlice.ts"],"names":[],"mappings":"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"}
|