mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 23:17:59 -05:00
fix(sockets events): remaining sockets events (#558)
* add sockets event for duplicate block * fix lint * add vertical ports event * fix lint --------- Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-Air.attlocal.net>
This commit is contained in:
committed by
GitHub
parent
9584f3cb57
commit
1a719470b8
@@ -12,9 +12,12 @@ interface ActionBarProps {
|
||||
}
|
||||
|
||||
export function ActionBar({ blockId, blockType, disabled = false }: ActionBarProps) {
|
||||
const { collaborativeRemoveBlock, collaborativeToggleBlockEnabled } = useCollaborativeWorkflow()
|
||||
const toggleBlockHandles = useWorkflowStore((state) => state.toggleBlockHandles)
|
||||
const duplicateBlock = useWorkflowStore((state) => state.duplicateBlock)
|
||||
const {
|
||||
collaborativeRemoveBlock,
|
||||
collaborativeToggleBlockEnabled,
|
||||
collaborativeDuplicateBlock,
|
||||
collaborativeToggleBlockHandles,
|
||||
} = useCollaborativeWorkflow()
|
||||
const isEnabled = useWorkflowStore((state) => state.blocks[blockId]?.enabled ?? true)
|
||||
const horizontalHandles = useWorkflowStore(
|
||||
(state) => state.blocks[blockId]?.horizontalHandles ?? false
|
||||
@@ -77,7 +80,7 @@ export function ActionBar({ blockId, blockType, disabled = false }: ActionBarPro
|
||||
size='sm'
|
||||
onClick={() => {
|
||||
if (!disabled) {
|
||||
duplicateBlock(blockId)
|
||||
collaborativeDuplicateBlock(blockId)
|
||||
}
|
||||
}}
|
||||
className={cn('text-gray-500', disabled && 'cursor-not-allowed opacity-50')}
|
||||
@@ -99,7 +102,7 @@ export function ActionBar({ blockId, blockType, disabled = false }: ActionBarPro
|
||||
size='sm'
|
||||
onClick={() => {
|
||||
if (!disabled) {
|
||||
toggleBlockHandles(blockId)
|
||||
collaborativeToggleBlockHandles(blockId)
|
||||
}
|
||||
}}
|
||||
className={cn('text-gray-500', disabled && 'cursor-not-allowed opacity-50')}
|
||||
|
||||
@@ -146,6 +146,26 @@ export function useCollaborativeWorkflow() {
|
||||
// For now, we'll use the existing toggle method
|
||||
workflowStore.toggleBlockAdvancedMode(payload.id)
|
||||
break
|
||||
case 'toggle-handles': {
|
||||
// Apply the handles toggle - we need to set the specific value to ensure consistency
|
||||
const currentBlock = workflowStore.blocks[payload.id]
|
||||
if (currentBlock && currentBlock.horizontalHandles !== payload.horizontalHandles) {
|
||||
workflowStore.toggleBlockHandles(payload.id)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'duplicate':
|
||||
// Apply the duplicate operation by adding the new block
|
||||
workflowStore.addBlock(
|
||||
payload.id,
|
||||
payload.type,
|
||||
payload.name,
|
||||
payload.position,
|
||||
payload.data,
|
||||
payload.parentId,
|
||||
payload.extent
|
||||
)
|
||||
break
|
||||
}
|
||||
} else if (target === 'edge') {
|
||||
switch (operation) {
|
||||
@@ -469,6 +489,100 @@ export function useCollaborativeWorkflow() {
|
||||
[workflowStore, emitWorkflowOperation]
|
||||
)
|
||||
|
||||
const collaborativeToggleBlockHandles = useCallback(
|
||||
(id: string) => {
|
||||
// Get the current state before toggling
|
||||
const currentBlock = workflowStore.blocks[id]
|
||||
if (!currentBlock) return
|
||||
|
||||
// Calculate the new horizontalHandles value
|
||||
const newHorizontalHandles = !currentBlock.horizontalHandles
|
||||
|
||||
// Apply locally first
|
||||
workflowStore.toggleBlockHandles(id)
|
||||
|
||||
// Emit with the calculated new value (don't rely on async state update)
|
||||
if (!isApplyingRemoteChange.current) {
|
||||
emitWorkflowOperation('toggle-handles', 'block', {
|
||||
id,
|
||||
horizontalHandles: newHorizontalHandles,
|
||||
})
|
||||
}
|
||||
},
|
||||
[workflowStore, emitWorkflowOperation]
|
||||
)
|
||||
|
||||
const collaborativeDuplicateBlock = useCallback(
|
||||
(sourceId: string) => {
|
||||
const sourceBlock = workflowStore.blocks[sourceId]
|
||||
if (!sourceBlock) return
|
||||
|
||||
// Generate new ID and calculate position
|
||||
const newId = crypto.randomUUID()
|
||||
const offsetPosition = {
|
||||
x: sourceBlock.position.x + 250,
|
||||
y: sourceBlock.position.y + 20,
|
||||
}
|
||||
|
||||
// Generate new name with numbering
|
||||
const match = sourceBlock.name.match(/(.*?)(\d+)?$/)
|
||||
const newName = match?.[2]
|
||||
? `${match[1]}${Number.parseInt(match[2]) + 1}`
|
||||
: `${sourceBlock.name} 1`
|
||||
|
||||
// Create the complete block data for the socket operation
|
||||
const duplicatedBlockData = {
|
||||
sourceId,
|
||||
id: newId,
|
||||
type: sourceBlock.type,
|
||||
name: newName,
|
||||
position: offsetPosition,
|
||||
data: sourceBlock.data ? JSON.parse(JSON.stringify(sourceBlock.data)) : {},
|
||||
subBlocks: sourceBlock.subBlocks ? JSON.parse(JSON.stringify(sourceBlock.subBlocks)) : {},
|
||||
outputs: sourceBlock.outputs ? JSON.parse(JSON.stringify(sourceBlock.outputs)) : {},
|
||||
parentId: sourceBlock.data?.parentId || null,
|
||||
extent: sourceBlock.data?.extent || null,
|
||||
enabled: sourceBlock.enabled ?? true,
|
||||
horizontalHandles: sourceBlock.horizontalHandles ?? true,
|
||||
isWide: sourceBlock.isWide ?? false,
|
||||
height: sourceBlock.height || 0,
|
||||
}
|
||||
|
||||
// Apply locally first using addBlock to ensure consistent IDs
|
||||
workflowStore.addBlock(
|
||||
newId,
|
||||
sourceBlock.type,
|
||||
newName,
|
||||
offsetPosition,
|
||||
sourceBlock.data ? JSON.parse(JSON.stringify(sourceBlock.data)) : {},
|
||||
sourceBlock.data?.parentId,
|
||||
sourceBlock.data?.extent
|
||||
)
|
||||
|
||||
// Copy subblock values to the new block
|
||||
const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId
|
||||
if (activeWorkflowId) {
|
||||
const subBlockValues =
|
||||
useSubBlockStore.getState().workflowValues[activeWorkflowId]?.[sourceId] || {}
|
||||
useSubBlockStore.setState((state) => ({
|
||||
workflowValues: {
|
||||
...state.workflowValues,
|
||||
[activeWorkflowId]: {
|
||||
...state.workflowValues[activeWorkflowId],
|
||||
[newId]: JSON.parse(JSON.stringify(subBlockValues)),
|
||||
},
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
// Then broadcast to other clients
|
||||
if (!isApplyingRemoteChange.current) {
|
||||
emitWorkflowOperation('duplicate', 'block', duplicatedBlockData)
|
||||
}
|
||||
},
|
||||
[workflowStore, emitWorkflowOperation]
|
||||
)
|
||||
|
||||
const collaborativeAddEdge = useCallback(
|
||||
(edge: Edge) => {
|
||||
// Apply locally first
|
||||
@@ -780,6 +894,8 @@ export function useCollaborativeWorkflow() {
|
||||
collaborativeUpdateParentId,
|
||||
collaborativeToggleBlockWide,
|
||||
collaborativeToggleBlockAdvancedMode,
|
||||
collaborativeToggleBlockHandles,
|
||||
collaborativeDuplicateBlock,
|
||||
collaborativeAddEdge,
|
||||
collaborativeRemoveEdge,
|
||||
collaborativeSetSubblockValue,
|
||||
|
||||
@@ -486,6 +486,122 @@ async function handleBlockOperationTx(
|
||||
break
|
||||
}
|
||||
|
||||
case 'toggle-handles': {
|
||||
if (!payload.id || payload.horizontalHandles === undefined) {
|
||||
throw new Error('Missing required fields for toggle handles operation')
|
||||
}
|
||||
|
||||
const updateResult = await tx
|
||||
.update(workflowBlocks)
|
||||
.set({
|
||||
horizontalHandles: payload.horizontalHandles,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(and(eq(workflowBlocks.id, payload.id), eq(workflowBlocks.workflowId, workflowId)))
|
||||
.returning({ id: workflowBlocks.id })
|
||||
|
||||
if (updateResult.length === 0) {
|
||||
throw new Error(`Block ${payload.id} not found in workflow ${workflowId}`)
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`Updated block handles: ${payload.id} -> ${payload.horizontalHandles ? 'horizontal' : 'vertical'}`
|
||||
)
|
||||
break
|
||||
}
|
||||
|
||||
case 'duplicate': {
|
||||
// Validate required fields for duplicate operation
|
||||
if (!payload.sourceId || !payload.id || !payload.type || !payload.name || !payload.position) {
|
||||
throw new Error('Missing required fields for duplicate block operation')
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`[SERVER] Duplicating block: ${payload.type} (${payload.sourceId} -> ${payload.id})`,
|
||||
{
|
||||
isSubflowType: isSubflowBlockType(payload.type),
|
||||
payload,
|
||||
}
|
||||
)
|
||||
|
||||
// Extract parentId and extent from payload
|
||||
const parentId = payload.parentId || null
|
||||
const extent = payload.extent || null
|
||||
|
||||
try {
|
||||
const insertData = {
|
||||
id: payload.id,
|
||||
workflowId,
|
||||
type: payload.type,
|
||||
name: payload.name,
|
||||
positionX: payload.position.x,
|
||||
positionY: payload.position.y,
|
||||
data: payload.data || {},
|
||||
subBlocks: payload.subBlocks || {},
|
||||
outputs: payload.outputs || {},
|
||||
parentId,
|
||||
extent,
|
||||
enabled: payload.enabled ?? true,
|
||||
horizontalHandles: payload.horizontalHandles ?? true,
|
||||
isWide: payload.isWide ?? false,
|
||||
height: payload.height || 0,
|
||||
}
|
||||
|
||||
await tx.insert(workflowBlocks).values(insertData)
|
||||
} catch (insertError) {
|
||||
logger.error(`[SERVER] ❌ Failed to insert duplicated block ${payload.id}:`, insertError)
|
||||
throw insertError
|
||||
}
|
||||
|
||||
// Auto-create subflow entry for loop/parallel blocks
|
||||
if (isSubflowBlockType(payload.type)) {
|
||||
try {
|
||||
const subflowConfig =
|
||||
payload.type === SubflowType.LOOP
|
||||
? {
|
||||
id: payload.id,
|
||||
nodes: [], // Empty initially, will be populated when child blocks are added
|
||||
iterations: payload.data?.count || DEFAULT_LOOP_ITERATIONS,
|
||||
loopType: payload.data?.loopType || 'for',
|
||||
forEachItems: payload.data?.collection || '',
|
||||
}
|
||||
: {
|
||||
id: payload.id,
|
||||
nodes: [], // Empty initially, will be populated when child blocks are added
|
||||
distribution: payload.data?.collection || '',
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`[SERVER] Auto-creating ${payload.type} subflow for duplicated block ${payload.id}:`,
|
||||
subflowConfig
|
||||
)
|
||||
|
||||
await tx.insert(workflowSubflows).values({
|
||||
id: payload.id,
|
||||
workflowId,
|
||||
type: payload.type,
|
||||
config: subflowConfig,
|
||||
})
|
||||
} catch (subflowError) {
|
||||
logger.error(
|
||||
`[SERVER] ❌ Failed to create ${payload.type} subflow for duplicated block ${payload.id}:`,
|
||||
subflowError
|
||||
)
|
||||
throw subflowError
|
||||
}
|
||||
}
|
||||
|
||||
// If this block has a parent, update the parent's subflow node list
|
||||
if (parentId) {
|
||||
await updateSubflowNodeList(tx, workflowId, parentId)
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`Duplicated block ${payload.sourceId} -> ${payload.id} (${payload.type}) in workflow ${workflowId}`
|
||||
)
|
||||
break
|
||||
}
|
||||
|
||||
// Add other block operations as needed
|
||||
default:
|
||||
logger.warn(`Unknown block operation: ${operation}`)
|
||||
|
||||
@@ -104,6 +104,7 @@ export async function verifyOperationPermission(
|
||||
'update-parent',
|
||||
'update-wide',
|
||||
'update-advanced-mode',
|
||||
'toggle-handles',
|
||||
'duplicate',
|
||||
],
|
||||
admin: [
|
||||
@@ -116,6 +117,7 @@ export async function verifyOperationPermission(
|
||||
'update-parent',
|
||||
'update-wide',
|
||||
'update-advanced-mode',
|
||||
'toggle-handles',
|
||||
'duplicate',
|
||||
],
|
||||
member: [
|
||||
@@ -128,6 +130,7 @@ export async function verifyOperationPermission(
|
||||
'update-parent',
|
||||
'update-wide',
|
||||
'update-advanced-mode',
|
||||
'toggle-handles',
|
||||
'duplicate',
|
||||
],
|
||||
viewer: ['update-position'], // Viewers can only move things around
|
||||
|
||||
@@ -15,19 +15,21 @@ export const BlockOperationSchema = z.object({
|
||||
'update-parent',
|
||||
'update-wide',
|
||||
'update-advanced-mode',
|
||||
'toggle-handles',
|
||||
'duplicate',
|
||||
]),
|
||||
target: z.literal('block'),
|
||||
payload: z.object({
|
||||
id: z.string(),
|
||||
sourceId: z.string().optional(), // For duplicate operations
|
||||
type: z.string().optional(),
|
||||
name: z.string().optional(),
|
||||
position: PositionSchema.optional(),
|
||||
data: z.record(z.any()).optional(),
|
||||
subBlocks: z.record(z.any()).optional(),
|
||||
outputs: z.record(z.any()).optional(),
|
||||
parentId: z.string().optional(),
|
||||
extent: z.enum(['parent']).optional(),
|
||||
parentId: z.string().nullable().optional(),
|
||||
extent: z.enum(['parent']).nullable().optional(),
|
||||
enabled: z.boolean().optional(),
|
||||
horizontalHandles: z.boolean().optional(),
|
||||
isWide: z.boolean().optional(),
|
||||
|
||||
Reference in New Issue
Block a user