Skip to content

Commit ec980ad

Browse files
committed
refactor sockets and undo/redo for batch additions and removals
1 parent dd9b414 commit ec980ad

File tree

14 files changed

+1020
-1611
lines changed

14 files changed

+1020
-1611
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/subflows/subflow-node.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export interface SubflowNodeData {
6161
*/
6262
export const SubflowNodeComponent = memo(({ data, id }: NodeProps<SubflowNodeData>) => {
6363
const { getNodes } = useReactFlow()
64-
const { collaborativeRemoveBlock } = useCollaborativeWorkflow()
64+
const { collaborativeBatchRemoveBlocks } = useCollaborativeWorkflow()
6565
const blockRef = useRef<HTMLDivElement>(null)
6666

6767
const currentWorkflow = useCurrentWorkflow()
@@ -184,7 +184,7 @@ export const SubflowNodeComponent = memo(({ data, id }: NodeProps<SubflowNodeDat
184184
variant='ghost'
185185
onClick={(e) => {
186186
e.stopPropagation()
187-
collaborativeRemoveBlock(id)
187+
collaborativeBatchRemoveBlocks([id])
188188
}}
189189
className='h-[14px] w-[14px] p-0 opacity-0 transition-opacity duration-100 group-hover:opacity-100'
190190
>

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/action-bar/action-bar.tsx

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@ import { Button, Copy, Tooltip, Trash2 } from '@/components/emcn'
44
import { cn } from '@/lib/core/utils/cn'
55
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
66
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
7+
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
8+
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
9+
import { getUniqueBlockName, prepareDuplicateBlockState } from '@/stores/workflows/utils'
710
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
811

12+
const DEFAULT_DUPLICATE_OFFSET = { x: 50, y: 50 }
13+
914
/**
1015
* Props for the ActionBar component
1116
*/
@@ -27,11 +32,39 @@ interface ActionBarProps {
2732
export const ActionBar = memo(
2833
function ActionBar({ blockId, blockType, disabled = false }: ActionBarProps) {
2934
const {
30-
collaborativeRemoveBlock,
35+
collaborativeBatchAddBlocks,
36+
collaborativeBatchRemoveBlocks,
3137
collaborativeToggleBlockEnabled,
32-
collaborativeDuplicateBlock,
3338
collaborativeToggleBlockHandles,
3439
} = useCollaborativeWorkflow()
40+
const { activeWorkflowId } = useWorkflowRegistry()
41+
const blocks = useWorkflowStore((state) => state.blocks)
42+
const subBlockStore = useSubBlockStore()
43+
44+
const handleDuplicateBlock = useCallback(() => {
45+
const sourceBlock = blocks[blockId]
46+
if (!sourceBlock) return
47+
48+
const newId = crypto.randomUUID()
49+
const newName = getUniqueBlockName(sourceBlock.name, blocks)
50+
const subBlockValues = subBlockStore.workflowValues[activeWorkflowId || '']?.[blockId] || {}
51+
52+
const { block, subBlockValues: filteredValues } = prepareDuplicateBlockState({
53+
sourceBlock,
54+
newId,
55+
newName,
56+
positionOffset: DEFAULT_DUPLICATE_OFFSET,
57+
subBlockValues,
58+
})
59+
60+
collaborativeBatchAddBlocks([block], [], {}, {}, { [newId]: filteredValues })
61+
}, [
62+
blockId,
63+
blocks,
64+
activeWorkflowId,
65+
subBlockStore.workflowValues,
66+
collaborativeBatchAddBlocks,
67+
])
3568

3669
/**
3770
* Optimized single store subscription for all block data
@@ -115,7 +148,7 @@ export const ActionBar = memo(
115148
onClick={(e) => {
116149
e.stopPropagation()
117150
if (!disabled) {
118-
collaborativeDuplicateBlock(blockId)
151+
handleDuplicateBlock()
119152
}
120153
}}
121154
className='hover:!text-[var(--text-inverse)] h-[23px] w-[23px] rounded-[8px] bg-[var(--surface-7)] p-0 text-[var(--text-secondary)] hover:bg-[var(--brand-secondary)]'
@@ -185,7 +218,7 @@ export const ActionBar = memo(
185218
onClick={(e) => {
186219
e.stopPropagation()
187220
if (!disabled) {
188-
collaborativeRemoveBlock(blockId)
221+
collaborativeBatchRemoveBlocks([blockId])
189222
}
190223
}}
191224
className='hover:!text-[var(--text-inverse)] h-[23px] w-[23px] rounded-[8px] bg-[var(--surface-7)] p-0 text-[var(--text-secondary)] hover:bg-[var(--brand-secondary)]'

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx

Lines changed: 80 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ import { usePanelEditorStore } from '@/stores/panel/editor/store'
6060
import { useGeneralStore } from '@/stores/settings/general/store'
6161
import { useWorkflowDiffStore } from '@/stores/workflow-diff/store'
6262
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
63-
import { getUniqueBlockName } from '@/stores/workflows/utils'
63+
import { getUniqueBlockName, prepareBlockState } from '@/stores/workflows/utils'
6464
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
6565

6666
/** Lazy-loaded components for non-critical UI that can load after initial render */
@@ -343,13 +343,12 @@ const WorkflowContent = React.memo(() => {
343343
}, [userPermissions, currentWorkflow.isSnapshotView])
344344

345345
const {
346-
collaborativeAddBlock: addBlock,
347346
collaborativeAddEdge: addEdge,
348-
collaborativeRemoveBlock: removeBlock,
349347
collaborativeRemoveEdge: removeEdge,
350348
collaborativeBatchUpdatePositions,
351349
collaborativeUpdateParentId: updateParentId,
352-
collaborativePasteBlocks,
350+
collaborativeBatchAddBlocks,
351+
collaborativeBatchRemoveBlocks,
353352
undo,
354353
redo,
355354
} = useCollaborativeWorkflow()
@@ -361,6 +360,39 @@ const WorkflowContent = React.memo(() => {
361360
[collaborativeBatchUpdatePositions]
362361
)
363362

363+
const addBlock = useCallback(
364+
(
365+
id: string,
366+
type: string,
367+
name: string,
368+
position: { x: number; y: number },
369+
data?: Record<string, unknown>,
370+
parentId?: string,
371+
extent?: 'parent',
372+
autoConnectEdge?: Edge,
373+
triggerMode?: boolean
374+
) => {
375+
const blockData: Record<string, unknown> = { ...(data || {}) }
376+
if (parentId) blockData.parentId = parentId
377+
if (extent) blockData.extent = extent
378+
379+
const block = prepareBlockState({
380+
id,
381+
type,
382+
name,
383+
position,
384+
data: blockData,
385+
parentId,
386+
extent,
387+
triggerMode,
388+
})
389+
390+
collaborativeBatchAddBlocks([block], autoConnectEdge ? [autoConnectEdge] : [], {}, {}, {})
391+
usePanelEditorStore.getState().setCurrentBlockId(id)
392+
},
393+
[collaborativeBatchAddBlocks]
394+
)
395+
364396
const { activeBlockIds, pendingBlocks, isDebugging } = useExecutionStore(
365397
useShallow((state) => ({
366398
activeBlockIds: state.activeBlockIds,
@@ -515,13 +547,43 @@ const WorkflowContent = React.memo(() => {
515547
if (selectedNodes.length > 0) {
516548
event.preventDefault()
517549
copyBlocks(selectedNodes.map((node) => node.id))
550+
} else {
551+
const currentBlockId = usePanelEditorStore.getState().currentBlockId
552+
if (currentBlockId && blocks[currentBlockId]) {
553+
event.preventDefault()
554+
copyBlocks([currentBlockId])
555+
}
518556
}
519557
} else if ((event.ctrlKey || event.metaKey) && event.key === 'v') {
520558
if (effectivePermissions.canEdit && hasClipboard()) {
521559
event.preventDefault()
522560
const pasteData = preparePasteData()
523561
if (pasteData) {
524-
collaborativePasteBlocks(pasteData)
562+
const pastedBlocks = Object.values(pasteData.blocks)
563+
const hasTriggerInPaste = pastedBlocks.some((block) =>
564+
TriggerUtils.isAnyTriggerType(block.type)
565+
)
566+
if (hasTriggerInPaste) {
567+
const existingTrigger = Object.values(blocks).find((block) =>
568+
TriggerUtils.isAnyTriggerType(block.type)
569+
)
570+
if (existingTrigger) {
571+
addNotification({
572+
level: 'error',
573+
message:
574+
'A workflow can only have one trigger block. Please remove the existing one before pasting.',
575+
workflowId: activeWorkflowId || undefined,
576+
})
577+
return
578+
}
579+
}
580+
collaborativeBatchAddBlocks(
581+
pastedBlocks,
582+
pasteData.edges,
583+
pasteData.loops,
584+
pasteData.parallels,
585+
pasteData.subBlockValues
586+
)
525587
}
526588
}
527589
}
@@ -540,9 +602,12 @@ const WorkflowContent = React.memo(() => {
540602
getNodes,
541603
copyBlocks,
542604
preparePasteData,
543-
collaborativePasteBlocks,
605+
collaborativeBatchAddBlocks,
544606
hasClipboard,
545607
effectivePermissions.canEdit,
608+
blocks,
609+
addNotification,
610+
activeWorkflowId,
546611
])
547612

548613
/**
@@ -2399,13 +2464,19 @@ const WorkflowContent = React.memo(() => {
23992464
}
24002465

24012466
event.preventDefault()
2402-
const primaryNode = selectedNodes[0]
2403-
removeBlock(primaryNode.id)
2467+
const selectedIds = selectedNodes.map((node) => node.id)
2468+
collaborativeBatchRemoveBlocks(selectedIds)
24042469
}
24052470

24062471
window.addEventListener('keydown', handleKeyDown)
24072472
return () => window.removeEventListener('keydown', handleKeyDown)
2408-
}, [selectedEdgeInfo, removeEdge, getNodes, removeBlock, effectivePermissions.canEdit])
2473+
}, [
2474+
selectedEdgeInfo,
2475+
removeEdge,
2476+
getNodes,
2477+
collaborativeBatchRemoveBlocks,
2478+
effectivePermissions.canEdit,
2479+
])
24092480

24102481
return (
24112482
<div className='flex h-full w-full flex-col overflow-hidden bg-[var(--bg)]'>

0 commit comments

Comments
 (0)