Converted canvas to React Flow

This commit is contained in:
Emir Karabeg
2025-01-14 14:07:13 -08:00
parent 0d64ae7a9a
commit 3740b78fac
7 changed files with 780 additions and 357 deletions

View File

@@ -1,3 +1,3 @@
export default function Home() {
return <div>Hello World</div>
return <div>Go to /w/1</div>
}

View File

@@ -1,241 +1,132 @@
'use client'
import { useState, useCallback, useEffect } from 'react'
import { BlockConfig } from '../components/blocks/types/block'
import { WorkflowBlock } from '../components/blocks/components/workflow-block/workflow-block'
import { useState, useCallback } from 'react'
import ReactFlow, {
Background,
Controls,
NodeProps,
NodeTypes,
EdgeTypes,
Connection,
Edge,
addEdge,
useNodesState,
useEdgesState,
XYPosition,
useReactFlow,
ReactFlowProvider,
} from 'reactflow'
import 'reactflow/dist/style.css'
import { getBlock } from '../components/blocks/configs'
import { CoordinateTransformer } from '../lib/coordinates'
import { WorkflowBlock } from '../components/blocks/components/workflow-block/workflow-block'
interface WorkflowBlock {
id: string
// Types
interface WorkflowNodeData {
type: string
position: { x: number; y: number }
config: BlockConfig
config: any // Consider adding a proper type for config
name: string
}
const ZOOM_SPEED = 0.005
const MIN_ZOOM = 0.5
const MAX_ZOOM = 2
const CANVAS_SIZE = 5000
// Node Components
const WorkflowNode = ({
data,
id,
xPos,
yPos,
}: NodeProps<WorkflowNodeData>) => (
<WorkflowBlock
id={id}
type={data.type}
position={{ x: xPos, y: yPos }}
config={data.config}
name={data.name}
/>
)
export default function Workflow() {
const [blocks, setBlocks] = useState<WorkflowBlock[]>([])
const [zoom, setZoom] = useState(1)
const [pan, setPan] = useState({ x: 0, y: 0 })
const [isPanning, setIsPanning] = useState(false)
const [startPanPos, setStartPanPos] = useState({ x: 0, y: 0 })
const nodeTypes: NodeTypes = {
workflowBlock: WorkflowNode,
}
// Initialize pan position after mount
useEffect(() => {
const { width, height } = CoordinateTransformer.getViewportDimensions()
setPan({
x: (width - CANVAS_SIZE) / 2,
y: (height - CANVAS_SIZE) / 2,
})
}, [])
// Main Canvas Component
function WorkflowCanvas() {
// State
const [nodes, setNodes, onNodesChange] = useNodesState([])
const [edges, setEdges, onEdgesChange] = useEdgesState([])
const { project } = useReactFlow()
const constrainPan = useCallback(
(newPan: { x: number; y: number }, currentZoom: number) => {
const { width, height } = CoordinateTransformer.getViewportDimensions()
// Calculate the scaled canvas size
const scaledCanvasWidth = CANVAS_SIZE * currentZoom
const scaledCanvasHeight = CANVAS_SIZE * currentZoom
// Calculate the maximum allowed pan values
const minX = Math.min(0, width - scaledCanvasWidth)
const minY = Math.min(0, height - scaledCanvasHeight)
return {
x: Math.min(0, Math.max(minX, newPan.x)),
y: Math.min(0, Math.max(minY, newPan.y)),
}
},
[]
// Handlers
const onConnect = useCallback(
(connection: Connection) => setEdges((eds) => addEdge(connection, eds)),
[setEdges]
)
const handleDragOver = (e: React.DragEvent) => {
e.preventDefault()
e.dataTransfer.dropEffect = 'copy'
}
const onDrop = useCallback(
(event: React.DragEvent) => {
event.preventDefault()
const handleDrop = (e: React.DragEvent) => {
e.preventDefault()
try {
const reactFlowBounds = event.currentTarget.getBoundingClientRect()
const position = project({
x: event.clientX - reactFlowBounds.left,
y: event.clientY - reactFlowBounds.top,
})
try {
const { type } = JSON.parse(e.dataTransfer.getData('application/json'))
const blockConfig = getBlock(type)
const { type } = JSON.parse(
event.dataTransfer.getData('application/json')
)
const blockConfig = getBlock(type)
if (!blockConfig) {
console.error('Invalid block type:', type)
return
}
if (!blockConfig) {
console.error('Invalid block type:', type)
return
}
const canvasElement = e.currentTarget as HTMLElement
const dropPoint = {
x: e.clientX,
y: e.clientY,
}
// Convert drop coordinates to canvas space
const canvasPoint = CoordinateTransformer.viewportToCanvas(
CoordinateTransformer.clientToViewport(dropPoint),
canvasElement
)
setBlocks((prev) => [
...prev,
{
const newNode = {
id: crypto.randomUUID(),
type,
position: canvasPoint,
config: blockConfig,
},
])
} catch (err) {
console.error('Error dropping block:', err)
}
}
type: 'workflowBlock',
position,
data: {
type,
config: blockConfig,
name: `${blockConfig.toolbar.title} ${
nodes.filter((n) => n.data.type === type).length + 1
}`,
},
}
const handleWheel = useCallback(
(e: React.WheelEvent) => {
if (e.ctrlKey || e.metaKey) {
e.preventDefault()
const delta = -e.deltaY * ZOOM_SPEED
setZoom((prevZoom) => {
if (
(prevZoom >= MAX_ZOOM && delta > 0) ||
(prevZoom <= MIN_ZOOM && delta < 0)
) {
return prevZoom
}
const newZoom = Math.min(
MAX_ZOOM,
Math.max(MIN_ZOOM, prevZoom + delta)
)
setPan((prevPan) => constrainPan(prevPan, newZoom))
return newZoom
})
} else {
setPan((prevPan) =>
constrainPan(
{
x: prevPan.x - e.deltaX,
y: prevPan.y - e.deltaY,
},
zoom
)
)
setNodes((nds) => [...nds, newNode])
} catch (err) {
console.error('Error dropping block:', err)
}
},
[constrainPan, zoom]
)
const handleMouseDown = useCallback(
(e: React.MouseEvent) => {
if (e.button === 1 || e.button === 0) {
setIsPanning(true)
const viewportPoint = CoordinateTransformer.clientToViewport({
x: e.clientX,
y: e.clientY,
})
setStartPanPos({
x: viewportPoint.x - pan.x,
y: viewportPoint.y - pan.y,
})
}
},
[pan]
)
const handleMouseMove = useCallback(
(e: React.MouseEvent) => {
if (isPanning) {
const viewportPoint = CoordinateTransformer.clientToViewport({
x: e.clientX,
y: e.clientY,
})
setPan((prevPan) =>
constrainPan(
{
x: viewportPoint.x - startPanPos.x,
y: viewportPoint.y - startPanPos.y,
},
zoom
)
)
}
},
[isPanning, startPanPos, zoom, constrainPan]
)
const handleMouseUp = useCallback(() => {
setIsPanning(false)
}, [])
useEffect(() => {
const preventDefaultZoom = (e: WheelEvent) => {
if (e.ctrlKey || e.metaKey) {
e.preventDefault()
}
}
document.addEventListener('wheel', preventDefaultZoom, { passive: false })
return () => document.removeEventListener('wheel', preventDefaultZoom)
}, [])
const updateBlockPosition = useCallback(
(id: string, newPosition: { x: number; y: number }) => {
setBlocks((prevBlocks) =>
prevBlocks.map((block) =>
block.id === id ? { ...block, position: newPosition } : block
)
)
},
[]
[project, nodes, setNodes]
)
return (
<div
className="w-full h-[calc(100vh-56px)] overflow-hidden select-none"
onWheel={handleWheel}
>
<div
className="w-full h-full bg-[#F5F5F5] relative cursor-grab active:cursor-grabbing"
style={{
backgroundImage: `radial-gradient(#D9D9D9 1px, transparent 1px)`,
backgroundSize: '20px 20px',
width: CANVAS_SIZE,
height: CANVAS_SIZE,
transform: `scale(${zoom}) translate(${pan.x}px, ${pan.y}px)`,
transformOrigin: '0 0',
}}
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseUp}
onDragOver={handleDragOver}
onDrop={handleDrop}
<div className="w-full h-[calc(100vh-56px)]">
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
nodeTypes={nodeTypes}
onDrop={onDrop}
onDragOver={(e) => e.preventDefault()}
fitView
maxZoom={1}
panOnScroll
>
{blocks.map((block, index) => {
const typeCount = blocks
.slice(0, index + 1)
.filter((b) => b.type === block.type).length
return (
<WorkflowBlock
key={block.id}
id={block.id}
type={block.type}
position={block.position}
config={block.config}
name={`${block.config.toolbar.title} ${typeCount}`}
onPositionUpdate={updateBlockPosition}
zoom={zoom}
/>
)
})}
</div>
<Background />
</ReactFlow>
</div>
)
}
export default function Workflow() {
return (
<ReactFlowProvider>
<WorkflowCanvas />
</ReactFlowProvider>
)
}

View File

@@ -10,6 +10,7 @@ export function ToolbarBlock({ config }: ToolbarBlockProps) {
'application/json',
JSON.stringify({ type: config.type })
)
e.dataTransfer.effectAllowed = 'move'
}
return (

View File

@@ -1,55 +1,132 @@
import { cn } from '@/lib/utils'
import { CoordinateTransformer } from '@/app/w/lib/coordinates'
import { useState, useCallback, useEffect } from 'react'
interface ConnectionPointProps {
position: 'top' | 'bottom'
}
interface ConnectionLine {
start: { x: number; y: number }
end: { x: number; y: number }
}
export function ConnectionPoint({ position }: ConnectionPointProps) {
const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
const [isDrawing, setIsDrawing] = useState(false)
const [line, setLine] = useState<ConnectionLine | null>(null)
const handleMouseDown = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation()
const canvasElement = e.currentTarget.closest(
'[style*="transform"]'
) as HTMLElement
if (!canvasElement) return
const elementPosition = CoordinateTransformer.getElementCanvasPosition(
e.currentTarget,
canvasElement
)
// Test distance calculation
const testPoint = {
x: e.clientX + 100,
y: e.clientY + 100,
// Get raw client coordinates
const startPoint = {
x: e.clientX,
y: e.clientY,
}
const distance = CoordinateTransformer.getTransformedDistance(
{ x: e.clientX, y: e.clientY },
testPoint,
// Use the same conversion pattern as drag and drop
const canvasPoint = CoordinateTransformer.viewportToCanvas(
CoordinateTransformer.clientToViewport(startPoint),
canvasElement
)
console.log({
viewport: CoordinateTransformer.getViewportDimensions(),
canvasTransform: CoordinateTransformer.getCanvasTransform(canvasElement),
elementPosition,
transformedDistance: distance,
setIsDrawing(true)
setLine({
start: canvasPoint,
end: canvasPoint,
})
}
}, [])
const handleMouseMove = useCallback(
(e: MouseEvent) => {
if (!isDrawing || !line) return
const canvasElement = document.querySelector(
'[style*="transform"]'
) as HTMLElement
if (!canvasElement) return
// Convert current mouse position to canvas coordinates
const currentPoint = CoordinateTransformer.viewportToCanvas(
CoordinateTransformer.clientToViewport({
x: e.clientX,
y: e.clientY,
}),
canvasElement
)
setLine((prev) =>
prev
? {
...prev,
end: currentPoint,
}
: null
)
},
[isDrawing, line]
)
const handleMouseUp = useCallback(() => {
setIsDrawing(false)
setLine(null)
}, [])
// Add and remove global mouse event listeners
useEffect(() => {
if (isDrawing) {
document.addEventListener('mousemove', handleMouseMove)
document.addEventListener('mouseup', handleMouseUp)
return () => {
document.removeEventListener('mousemove', handleMouseMove)
document.removeEventListener('mouseup', handleMouseUp)
}
}
}, [isDrawing, handleMouseMove, handleMouseUp])
return (
<div
data-connection-point
onClick={handleClick}
className={cn(
'absolute left-1/2 -translate-x-1/2 w-3 h-3',
'bg-white rounded-full border opacity-0 group-hover:opacity-100',
'transition-opacity duration-200 cursor-crosshair hover:border-blue-500',
'hover:scale-110 hover:shadow-sm',
position === 'top'
? '-translate-y-1/2 top-0'
: 'translate-y-1/2 bottom-0'
<>
<div
data-connection-point
onMouseDown={handleMouseDown}
className={cn(
'absolute left-1/2 -translate-x-1/2 w-3 h-3',
'bg-white rounded-full border opacity-0 group-hover:opacity-100',
'transition-opacity duration-200 cursor-crosshair hover:border-blue-500',
'hover:scale-110 hover:shadow-sm',
position === 'top'
? '-translate-y-1/2 top-0'
: 'translate-y-1/2 bottom-0'
)}
/>
{line && (
<svg
style={{
position: 'fixed',
top: 0,
left: 0,
width: '100vw',
height: '100vh',
zIndex: 9999,
pointerEvents: 'none',
}}
>
<line
x1={line.start.x}
y1={line.start.y}
x2={line.end.x}
y2={line.end.y}
stroke="rgb(59 130 246)"
strokeWidth="2"
strokeDasharray="5,5"
/>
</svg>
)}
/>
</>
)
}

View File

@@ -1,131 +1,53 @@
import { Card } from '@/components/ui/card'
import { BlockConfig, SubBlockConfig } from '../../types/block'
import { cn } from '@/lib/utils'
import { SubBlock } from './components/sub-block/sub-block'
import { useCallback, useState, MouseEvent, useEffect } from 'react'
import { ConnectionPoint } from './components/connection/connection-point'
import { Handle, Position } from 'reactflow'
export interface WorkflowBlockProps {
interface WorkflowBlockProps {
id: string
type: string
position: { x: number; y: number }
config: BlockConfig
name: string
onPositionUpdate: (id: string, position: { x: number; y: number }) => void
zoom: number
}
function groupSubBlocks(subBlocks: SubBlockConfig[]) {
const rows: SubBlockConfig[][] = []
let currentRow: SubBlockConfig[] = []
let currentRowWidth = 0
export function WorkflowBlock({ id, type, config, name }: WorkflowBlockProps) {
const { toolbar, workflow } = config
subBlocks.forEach((block) => {
const blockWidth = block.layout === 'half' ? 0.5 : 1
function groupSubBlocks(subBlocks: SubBlockConfig[]) {
const rows: SubBlockConfig[][] = []
let currentRow: SubBlockConfig[] = []
let currentRowWidth = 0
if (currentRowWidth + blockWidth > 1) {
rows.push([...currentRow])
currentRow = [block]
currentRowWidth = blockWidth
} else {
currentRow.push(block)
currentRowWidth += blockWidth
subBlocks.forEach((block) => {
const blockWidth = block.layout === 'half' ? 0.5 : 1
if (currentRowWidth + blockWidth > 1) {
rows.push([...currentRow])
currentRow = [block]
currentRowWidth = blockWidth
} else {
currentRow.push(block)
currentRowWidth += blockWidth
}
})
if (currentRow.length > 0) {
rows.push(currentRow)
}
})
if (currentRow.length > 0) {
rows.push(currentRow)
return rows
}
return rows
}
export function WorkflowBlock({
id,
type,
position,
config,
name,
onPositionUpdate,
zoom,
}: WorkflowBlockProps) {
const [isDragging, setIsDragging] = useState(false)
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 })
const handleMouseDown = useCallback(
(e: MouseEvent) => {
// Don't handle drag if clicking on a connection point
if ((e.target as HTMLElement).closest('[data-connection-point]')) {
return
}
e.stopPropagation()
setIsDragging(true)
// Account for the sidebar width (344px) and control bar height (56px)
const sidebarWidth = 344 // 72px (sidebar) + 272px (toolbar)
const controlBarHeight = 56
const rect = e.currentTarget.getBoundingClientRect()
setDragOffset({
x: (e.clientX - sidebarWidth) / zoom - position.x,
y: (e.clientY - controlBarHeight) / zoom - position.y,
})
},
[zoom, position]
)
const handleMouseMove = useCallback(
(e: MouseEvent) => {
if (isDragging) {
e.stopPropagation()
// Account for the sidebar width and control bar height
const sidebarWidth = 344
const controlBarHeight = 56
const newX = (e.clientX - sidebarWidth) / zoom - dragOffset.x
const newY = (e.clientY - controlBarHeight) / zoom - dragOffset.y
onPositionUpdate(id, { x: newX, y: newY })
}
},
[id, isDragging, dragOffset, onPositionUpdate, zoom]
)
const handleMouseUp = useCallback(() => {
setIsDragging(false)
}, [])
// Add event listeners to handle dragging outside the block
useEffect(() => {
if (isDragging) {
document.addEventListener('mousemove', handleMouseMove as any)
document.addEventListener('mouseup', handleMouseUp)
return () => {
document.removeEventListener('mousemove', handleMouseMove as any)
document.removeEventListener('mouseup', handleMouseUp)
}
}
}, [isDragging, handleMouseMove, handleMouseUp])
const { toolbar, workflow } = config
const subBlockRows = groupSubBlocks(workflow.subBlocks)
return (
<Card
className={cn(
'absolute w-[320px] shadow-md cursor-move select-none group',
'transform -translate-x-1/2 -translate-y-1/2',
isDragging && 'pointer-events-none'
)}
style={{
left: `${position.x}px`,
top: `${position.y}px`,
}}
onMouseDown={handleMouseDown}
>
<ConnectionPoint position="top" />
<Card className="w-[320px] shadow-md select-none group">
<Handle
type="target"
position={Position.Top}
className="w-3 h-3 bg-white border-2 border-blue-500"
/>
<div className="flex items-center gap-3 p-3 border-b">
<div
@@ -143,10 +65,9 @@ export function WorkflowBlock({
{row.map((subBlock, blockIndex) => (
<div
key={`${id}-${rowIndex}-${blockIndex}`}
className={cn(
'space-y-1',
className={`space-y-1 ${
subBlock.layout === 'half' ? 'flex-1' : 'w-full'
)}
}`}
>
<SubBlock config={subBlock} />
</div>
@@ -155,7 +76,11 @@ export function WorkflowBlock({
))}
</div>
<ConnectionPoint position="bottom" />
<Handle
type="source"
position={Position.Bottom}
className="w-3 h-3 bg-white border-2 border-blue-500"
/>
</Card>
)
}

528
package-lock.json generated
View File

@@ -24,6 +24,7 @@
"next": "15.1.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"reactflow": "^11.11.4",
"tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7"
},
@@ -1831,6 +1832,108 @@
"integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==",
"license": "MIT"
},
"node_modules/@reactflow/background": {
"version": "11.3.14",
"resolved": "https://registry.npmjs.org/@reactflow/background/-/background-11.3.14.tgz",
"integrity": "sha512-Gewd7blEVT5Lh6jqrvOgd4G6Qk17eGKQfsDXgyRSqM+CTwDqRldG2LsWN4sNeno6sbqVIC2fZ+rAUBFA9ZEUDA==",
"license": "MIT",
"dependencies": {
"@reactflow/core": "11.11.4",
"classcat": "^5.0.3",
"zustand": "^4.4.1"
},
"peerDependencies": {
"react": ">=17",
"react-dom": ">=17"
}
},
"node_modules/@reactflow/controls": {
"version": "11.2.14",
"resolved": "https://registry.npmjs.org/@reactflow/controls/-/controls-11.2.14.tgz",
"integrity": "sha512-MiJp5VldFD7FrqaBNIrQ85dxChrG6ivuZ+dcFhPQUwOK3HfYgX2RHdBua+gx+40p5Vw5It3dVNp/my4Z3jF0dw==",
"license": "MIT",
"dependencies": {
"@reactflow/core": "11.11.4",
"classcat": "^5.0.3",
"zustand": "^4.4.1"
},
"peerDependencies": {
"react": ">=17",
"react-dom": ">=17"
}
},
"node_modules/@reactflow/core": {
"version": "11.11.4",
"resolved": "https://registry.npmjs.org/@reactflow/core/-/core-11.11.4.tgz",
"integrity": "sha512-H4vODklsjAq3AMq6Np4LE12i1I4Ta9PrDHuBR9GmL8uzTt2l2jh4CiQbEMpvMDcp7xi4be0hgXj+Ysodde/i7Q==",
"license": "MIT",
"dependencies": {
"@types/d3": "^7.4.0",
"@types/d3-drag": "^3.0.1",
"@types/d3-selection": "^3.0.3",
"@types/d3-zoom": "^3.0.1",
"classcat": "^5.0.3",
"d3-drag": "^3.0.0",
"d3-selection": "^3.0.0",
"d3-zoom": "^3.0.0",
"zustand": "^4.4.1"
},
"peerDependencies": {
"react": ">=17",
"react-dom": ">=17"
}
},
"node_modules/@reactflow/minimap": {
"version": "11.7.14",
"resolved": "https://registry.npmjs.org/@reactflow/minimap/-/minimap-11.7.14.tgz",
"integrity": "sha512-mpwLKKrEAofgFJdkhwR5UQ1JYWlcAAL/ZU/bctBkuNTT1yqV+y0buoNVImsRehVYhJwffSWeSHaBR5/GJjlCSQ==",
"license": "MIT",
"dependencies": {
"@reactflow/core": "11.11.4",
"@types/d3-selection": "^3.0.3",
"@types/d3-zoom": "^3.0.1",
"classcat": "^5.0.3",
"d3-selection": "^3.0.0",
"d3-zoom": "^3.0.0",
"zustand": "^4.4.1"
},
"peerDependencies": {
"react": ">=17",
"react-dom": ">=17"
}
},
"node_modules/@reactflow/node-resizer": {
"version": "2.2.14",
"resolved": "https://registry.npmjs.org/@reactflow/node-resizer/-/node-resizer-2.2.14.tgz",
"integrity": "sha512-fwqnks83jUlYr6OHcdFEedumWKChTHRGw/kbCxj0oqBd+ekfs+SIp4ddyNU0pdx96JIm5iNFS0oNrmEiJbbSaA==",
"license": "MIT",
"dependencies": {
"@reactflow/core": "11.11.4",
"classcat": "^5.0.4",
"d3-drag": "^3.0.0",
"d3-selection": "^3.0.0",
"zustand": "^4.4.1"
},
"peerDependencies": {
"react": ">=17",
"react-dom": ">=17"
}
},
"node_modules/@reactflow/node-toolbar": {
"version": "1.3.14",
"resolved": "https://registry.npmjs.org/@reactflow/node-toolbar/-/node-toolbar-1.3.14.tgz",
"integrity": "sha512-rbynXQnH/xFNu4P9H+hVqlEUafDCkEoCy0Dg9mG22Sg+rY/0ck6KkrAQrYrTgXusd+cEJOMK0uOOFCK2/5rSGQ==",
"license": "MIT",
"dependencies": {
"@reactflow/core": "11.11.4",
"classcat": "^5.0.3",
"zustand": "^4.4.1"
},
"peerDependencies": {
"react": ">=17",
"react-dom": ">=17"
}
},
"node_modules/@swc/counter": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
@@ -1846,6 +1949,265 @@
"tslib": "^2.8.0"
}
},
"node_modules/@types/d3": {
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz",
"integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==",
"license": "MIT",
"dependencies": {
"@types/d3-array": "*",
"@types/d3-axis": "*",
"@types/d3-brush": "*",
"@types/d3-chord": "*",
"@types/d3-color": "*",
"@types/d3-contour": "*",
"@types/d3-delaunay": "*",
"@types/d3-dispatch": "*",
"@types/d3-drag": "*",
"@types/d3-dsv": "*",
"@types/d3-ease": "*",
"@types/d3-fetch": "*",
"@types/d3-force": "*",
"@types/d3-format": "*",
"@types/d3-geo": "*",
"@types/d3-hierarchy": "*",
"@types/d3-interpolate": "*",
"@types/d3-path": "*",
"@types/d3-polygon": "*",
"@types/d3-quadtree": "*",
"@types/d3-random": "*",
"@types/d3-scale": "*",
"@types/d3-scale-chromatic": "*",
"@types/d3-selection": "*",
"@types/d3-shape": "*",
"@types/d3-time": "*",
"@types/d3-time-format": "*",
"@types/d3-timer": "*",
"@types/d3-transition": "*",
"@types/d3-zoom": "*"
}
},
"node_modules/@types/d3-array": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz",
"integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==",
"license": "MIT"
},
"node_modules/@types/d3-axis": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz",
"integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==",
"license": "MIT",
"dependencies": {
"@types/d3-selection": "*"
}
},
"node_modules/@types/d3-brush": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz",
"integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==",
"license": "MIT",
"dependencies": {
"@types/d3-selection": "*"
}
},
"node_modules/@types/d3-chord": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz",
"integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==",
"license": "MIT"
},
"node_modules/@types/d3-color": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
"license": "MIT"
},
"node_modules/@types/d3-contour": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz",
"integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==",
"license": "MIT",
"dependencies": {
"@types/d3-array": "*",
"@types/geojson": "*"
}
},
"node_modules/@types/d3-delaunay": {
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
"integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==",
"license": "MIT"
},
"node_modules/@types/d3-dispatch": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz",
"integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==",
"license": "MIT"
},
"node_modules/@types/d3-drag": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz",
"integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==",
"license": "MIT",
"dependencies": {
"@types/d3-selection": "*"
}
},
"node_modules/@types/d3-dsv": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz",
"integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==",
"license": "MIT"
},
"node_modules/@types/d3-ease": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
"integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
"license": "MIT"
},
"node_modules/@types/d3-fetch": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz",
"integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==",
"license": "MIT",
"dependencies": {
"@types/d3-dsv": "*"
}
},
"node_modules/@types/d3-force": {
"version": "3.0.10",
"resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz",
"integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==",
"license": "MIT"
},
"node_modules/@types/d3-format": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz",
"integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==",
"license": "MIT"
},
"node_modules/@types/d3-geo": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz",
"integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==",
"license": "MIT",
"dependencies": {
"@types/geojson": "*"
}
},
"node_modules/@types/d3-hierarchy": {
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz",
"integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==",
"license": "MIT"
},
"node_modules/@types/d3-interpolate": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
"license": "MIT",
"dependencies": {
"@types/d3-color": "*"
}
},
"node_modules/@types/d3-path": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz",
"integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==",
"license": "MIT"
},
"node_modules/@types/d3-polygon": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz",
"integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==",
"license": "MIT"
},
"node_modules/@types/d3-quadtree": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz",
"integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==",
"license": "MIT"
},
"node_modules/@types/d3-random": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz",
"integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==",
"license": "MIT"
},
"node_modules/@types/d3-scale": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz",
"integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==",
"license": "MIT",
"dependencies": {
"@types/d3-time": "*"
}
},
"node_modules/@types/d3-scale-chromatic": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
"integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==",
"license": "MIT"
},
"node_modules/@types/d3-selection": {
"version": "3.0.11",
"resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz",
"integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==",
"license": "MIT"
},
"node_modules/@types/d3-shape": {
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz",
"integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==",
"license": "MIT",
"dependencies": {
"@types/d3-path": "*"
}
},
"node_modules/@types/d3-time": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
"integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
"license": "MIT"
},
"node_modules/@types/d3-time-format": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz",
"integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==",
"license": "MIT"
},
"node_modules/@types/d3-timer": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
"license": "MIT"
},
"node_modules/@types/d3-transition": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz",
"integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==",
"license": "MIT",
"dependencies": {
"@types/d3-selection": "*"
}
},
"node_modules/@types/d3-zoom": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz",
"integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==",
"license": "MIT",
"dependencies": {
"@types/d3-interpolate": "*",
"@types/d3-selection": "*"
}
},
"node_modules/@types/geojson": {
"version": "7946.0.15",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.15.tgz",
"integrity": "sha512-9oSxFzDCT2Rj6DfcHF8G++jxBKS7mBqXl5xrRW+Kbvjry6Uduya2iiwqHPhVXpasAVMBYKkEPGgKhd3+/HZ6xA==",
"license": "MIT"
},
"node_modules/@types/node": {
"version": "20.17.11",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.11.tgz",
@@ -2072,6 +2434,12 @@
"url": "https://polar.sh/cva"
}
},
"node_modules/classcat": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz",
"integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==",
"license": "MIT"
},
"node_modules/client-only": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
@@ -2653,6 +3021,111 @@
"devOptional": true,
"license": "MIT"
},
"node_modules/d3-color": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-dispatch": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
"integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-drag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz",
"integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
"license": "ISC",
"dependencies": {
"d3-dispatch": "1 - 3",
"d3-selection": "3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-ease": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-interpolate": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
"license": "ISC",
"dependencies": {
"d3-color": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-selection": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-timer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-transition": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz",
"integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
"license": "ISC",
"dependencies": {
"d3-color": "1 - 3",
"d3-dispatch": "1 - 3",
"d3-ease": "1 - 3",
"d3-interpolate": "1 - 3",
"d3-timer": "1 - 3"
},
"engines": {
"node": ">=12"
},
"peerDependencies": {
"d3-selection": "2 - 3"
}
},
"node_modules/d3-zoom": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz",
"integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
"license": "ISC",
"dependencies": {
"d3-dispatch": "1 - 3",
"d3-drag": "2 - 3",
"d3-interpolate": "1 - 3",
"d3-selection": "2 - 3",
"d3-transition": "2 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/detect-libc": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
@@ -3518,6 +3991,24 @@
}
}
},
"node_modules/reactflow": {
"version": "11.11.4",
"resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.11.4.tgz",
"integrity": "sha512-70FOtJkUWH3BAOsN+LU9lCrKoKbtOPnz2uq0CV2PLdNSwxTXOhCbsZr50GmZ+Rtw3jx8Uv7/vBFtCGixLfd4Og==",
"license": "MIT",
"dependencies": {
"@reactflow/background": "11.3.14",
"@reactflow/controls": "11.2.14",
"@reactflow/core": "11.11.4",
"@reactflow/minimap": "11.7.14",
"@reactflow/node-resizer": "2.2.14",
"@reactflow/node-toolbar": "1.3.14"
},
"peerDependencies": {
"react": ">=17",
"react-dom": ">=17"
}
},
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -4092,6 +4583,15 @@
}
}
},
"node_modules/use-sync-external-store": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz",
"integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -4215,6 +4715,34 @@
"engines": {
"node": ">= 14"
}
},
"node_modules/zustand": {
"version": "4.5.6",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.6.tgz",
"integrity": "sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ==",
"license": "MIT",
"dependencies": {
"use-sync-external-store": "^1.2.2"
},
"engines": {
"node": ">=12.7.0"
},
"peerDependencies": {
"@types/react": ">=16.8",
"immer": ">=9.0.6",
"react": ">=16.8"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"immer": {
"optional": true
},
"react": {
"optional": true
}
}
}
}
}

View File

@@ -25,6 +25,7 @@
"next": "15.1.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"reactflow": "^11.11.4",
"tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7"
},