mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-10 23:48:09 -05:00
improvement(add-block): intuitive autoconnect + positioning (#2714)
* improvement(add-block): intuitive autoconnect + positioning * cleanup code
This commit is contained in:
committed by
GitHub
parent
142c9a0428
commit
3b4f7d6adb
@@ -19,7 +19,6 @@ import { useShallow } from 'zustand/react/shallow'
|
||||
import { useSession } from '@/lib/auth/auth-client'
|
||||
import type { OAuthConnectEventDetail } from '@/lib/copilot/tools/client/other/oauth-request-access'
|
||||
import type { OAuthProvider } from '@/lib/oauth'
|
||||
import { DEFAULT_HORIZONTAL_SPACING } from '@/lib/workflows/autolayout/constants'
|
||||
import { BLOCK_DIMENSIONS, CONTAINER_DIMENSIONS } from '@/lib/workflows/blocks/block-dimensions'
|
||||
import { TriggerUtils } from '@/lib/workflows/triggers/triggers'
|
||||
import { useWorkspacePermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
|
||||
@@ -89,6 +88,26 @@ const logger = createLogger('Workflow')
|
||||
|
||||
const DEFAULT_PASTE_OFFSET = { x: 50, y: 50 }
|
||||
|
||||
/**
|
||||
* Gets the center of the current viewport in flow coordinates
|
||||
*/
|
||||
function getViewportCenter(
|
||||
screenToFlowPosition: (pos: { x: number; y: number }) => { x: number; y: number }
|
||||
): { x: number; y: number } {
|
||||
const flowContainer = document.querySelector('.react-flow')
|
||||
if (!flowContainer) {
|
||||
return screenToFlowPosition({
|
||||
x: window.innerWidth / 2,
|
||||
y: window.innerHeight / 2,
|
||||
})
|
||||
}
|
||||
const rect = flowContainer.getBoundingClientRect()
|
||||
return screenToFlowPosition({
|
||||
x: rect.width / 2,
|
||||
y: rect.height / 2,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the offset to paste blocks at viewport center
|
||||
*/
|
||||
@@ -125,14 +144,7 @@ function calculatePasteOffset(
|
||||
)
|
||||
const clipboardCenter = { x: (minX + maxX) / 2, y: (minY + maxY) / 2 }
|
||||
|
||||
const flowContainer = document.querySelector('.react-flow')
|
||||
if (!flowContainer) return DEFAULT_PASTE_OFFSET
|
||||
|
||||
const rect = flowContainer.getBoundingClientRect()
|
||||
const viewportCenter = screenToFlowPosition({
|
||||
x: rect.width / 2,
|
||||
y: rect.height / 2,
|
||||
})
|
||||
const viewportCenter = getViewportCenter(screenToFlowPosition)
|
||||
|
||||
return {
|
||||
x: viewportCenter.x - clipboardCenter.x,
|
||||
@@ -1312,65 +1324,13 @@ const WorkflowContent = React.memo(() => {
|
||||
if (!type) return
|
||||
if (type === 'connectionBlock') return
|
||||
|
||||
// Calculate smart position - to the right of existing root-level blocks
|
||||
const calculateSmartPosition = (): { x: number; y: number } => {
|
||||
// Get all root-level blocks (no parentId)
|
||||
const rootBlocks = Object.values(blocks).filter((b) => !b.data?.parentId)
|
||||
const basePosition = getViewportCenter(screenToFlowPosition)
|
||||
|
||||
if (rootBlocks.length === 0) {
|
||||
// No blocks yet, use viewport center
|
||||
return screenToFlowPosition({
|
||||
x: window.innerWidth / 2,
|
||||
y: window.innerHeight / 2,
|
||||
})
|
||||
}
|
||||
|
||||
// Find the rightmost block
|
||||
let maxRight = Number.NEGATIVE_INFINITY
|
||||
let rightmostBlockY = 0
|
||||
for (const block of rootBlocks) {
|
||||
const blockWidth =
|
||||
block.type === 'loop' || block.type === 'parallel'
|
||||
? block.data?.width || CONTAINER_DIMENSIONS.DEFAULT_WIDTH
|
||||
: BLOCK_DIMENSIONS.FIXED_WIDTH
|
||||
const blockRight = block.position.x + blockWidth
|
||||
if (blockRight > maxRight) {
|
||||
maxRight = blockRight
|
||||
rightmostBlockY = block.position.y
|
||||
}
|
||||
}
|
||||
|
||||
// Position to the right with autolayout spacing
|
||||
const position = {
|
||||
x: maxRight + DEFAULT_HORIZONTAL_SPACING,
|
||||
y: rightmostBlockY,
|
||||
}
|
||||
|
||||
// Ensure position doesn't overlap any container
|
||||
let container = isPointInLoopNode(position)
|
||||
while (container) {
|
||||
position.x =
|
||||
container.loopPosition.x + container.dimensions.width + DEFAULT_HORIZONTAL_SPACING
|
||||
container = isPointInLoopNode(position)
|
||||
}
|
||||
|
||||
return position
|
||||
}
|
||||
|
||||
const basePosition = calculateSmartPosition()
|
||||
|
||||
// Special handling for container nodes (loop or parallel)
|
||||
if (type === 'loop' || type === 'parallel') {
|
||||
const id = crypto.randomUUID()
|
||||
const baseName = type === 'loop' ? 'Loop' : 'Parallel'
|
||||
const name = getUniqueBlockName(baseName, blocks)
|
||||
|
||||
const autoConnectEdge = tryCreateAutoConnectEdge(basePosition, id, {
|
||||
blockType: type,
|
||||
targetParentId: null,
|
||||
})
|
||||
|
||||
// Add the container node with default dimensions and auto-connect edge
|
||||
addBlock(
|
||||
id,
|
||||
type,
|
||||
@@ -1383,7 +1343,7 @@ const WorkflowContent = React.memo(() => {
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
autoConnectEdge
|
||||
undefined
|
||||
)
|
||||
|
||||
return
|
||||
@@ -1395,24 +1355,13 @@ const WorkflowContent = React.memo(() => {
|
||||
return
|
||||
}
|
||||
|
||||
// Check trigger constraints first
|
||||
if (checkTriggerConstraints(type)) return
|
||||
|
||||
// Create a new block with a unique ID
|
||||
const id = crypto.randomUUID()
|
||||
// Prefer semantic default names for triggers; then ensure unique numbering centrally
|
||||
const defaultTriggerName = TriggerUtils.getDefaultTriggerName(type)
|
||||
const baseName = defaultTriggerName || blockConfig.name
|
||||
const name = getUniqueBlockName(baseName, blocks)
|
||||
|
||||
const autoConnectEdge = tryCreateAutoConnectEdge(basePosition, id, {
|
||||
blockType: type,
|
||||
enableTriggerMode,
|
||||
targetParentId: null,
|
||||
})
|
||||
|
||||
// Add the block to the workflow with auto-connect edge
|
||||
// Enable trigger mode if this is a trigger-capable block from the triggers tab
|
||||
addBlock(
|
||||
id,
|
||||
type,
|
||||
@@ -1421,7 +1370,7 @@ const WorkflowContent = React.memo(() => {
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
autoConnectEdge,
|
||||
undefined,
|
||||
enableTriggerMode
|
||||
)
|
||||
}
|
||||
@@ -1438,11 +1387,7 @@ const WorkflowContent = React.memo(() => {
|
||||
screenToFlowPosition,
|
||||
blocks,
|
||||
addBlock,
|
||||
tryCreateAutoConnectEdge,
|
||||
isPointInLoopNode,
|
||||
effectivePermissions.canEdit,
|
||||
addNotification,
|
||||
activeWorkflowId,
|
||||
checkTriggerConstraints,
|
||||
])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user