@@ -146,6 +146,26 @@ export function useCollaborativeWorkflow() {
146146 // For now, we'll use the existing toggle method
147147 workflowStore . toggleBlockAdvancedMode ( payload . id )
148148 break
149+ case 'toggle-handles' : {
150+ // Apply the handles toggle - we need to set the specific value to ensure consistency
151+ const currentBlock = workflowStore . blocks [ payload . id ]
152+ if ( currentBlock && currentBlock . horizontalHandles !== payload . horizontalHandles ) {
153+ workflowStore . toggleBlockHandles ( payload . id )
154+ }
155+ break
156+ }
157+ case 'duplicate' :
158+ // Apply the duplicate operation by adding the new block
159+ workflowStore . addBlock (
160+ payload . id ,
161+ payload . type ,
162+ payload . name ,
163+ payload . position ,
164+ payload . data ,
165+ payload . parentId ,
166+ payload . extent
167+ )
168+ break
149169 }
150170 } else if ( target === 'edge' ) {
151171 switch ( operation ) {
@@ -469,6 +489,100 @@ export function useCollaborativeWorkflow() {
469489 [ workflowStore , emitWorkflowOperation ]
470490 )
471491
492+ const collaborativeToggleBlockHandles = useCallback (
493+ ( id : string ) => {
494+ // Get the current state before toggling
495+ const currentBlock = workflowStore . blocks [ id ]
496+ if ( ! currentBlock ) return
497+
498+ // Calculate the new horizontalHandles value
499+ const newHorizontalHandles = ! currentBlock . horizontalHandles
500+
501+ // Apply locally first
502+ workflowStore . toggleBlockHandles ( id )
503+
504+ // Emit with the calculated new value (don't rely on async state update)
505+ if ( ! isApplyingRemoteChange . current ) {
506+ emitWorkflowOperation ( 'toggle-handles' , 'block' , {
507+ id,
508+ horizontalHandles : newHorizontalHandles ,
509+ } )
510+ }
511+ } ,
512+ [ workflowStore , emitWorkflowOperation ]
513+ )
514+
515+ const collaborativeDuplicateBlock = useCallback (
516+ ( sourceId : string ) => {
517+ const sourceBlock = workflowStore . blocks [ sourceId ]
518+ if ( ! sourceBlock ) return
519+
520+ // Generate new ID and calculate position
521+ const newId = crypto . randomUUID ( )
522+ const offsetPosition = {
523+ x : sourceBlock . position . x + 250 ,
524+ y : sourceBlock . position . y + 20 ,
525+ }
526+
527+ // Generate new name with numbering
528+ const match = sourceBlock . name . match ( / ( .* ?) ( \d + ) ? $ / )
529+ const newName = match ?. [ 2 ]
530+ ? `${ match [ 1 ] } ${ Number . parseInt ( match [ 2 ] ) + 1 } `
531+ : `${ sourceBlock . name } 1`
532+
533+ // Create the complete block data for the socket operation
534+ const duplicatedBlockData = {
535+ sourceId,
536+ id : newId ,
537+ type : sourceBlock . type ,
538+ name : newName ,
539+ position : offsetPosition ,
540+ data : sourceBlock . data ? JSON . parse ( JSON . stringify ( sourceBlock . data ) ) : { } ,
541+ subBlocks : sourceBlock . subBlocks ? JSON . parse ( JSON . stringify ( sourceBlock . subBlocks ) ) : { } ,
542+ outputs : sourceBlock . outputs ? JSON . parse ( JSON . stringify ( sourceBlock . outputs ) ) : { } ,
543+ parentId : sourceBlock . data ?. parentId || null ,
544+ extent : sourceBlock . data ?. extent || null ,
545+ enabled : sourceBlock . enabled ?? true ,
546+ horizontalHandles : sourceBlock . horizontalHandles ?? true ,
547+ isWide : sourceBlock . isWide ?? false ,
548+ height : sourceBlock . height || 0 ,
549+ }
550+
551+ // Apply locally first using addBlock to ensure consistent IDs
552+ workflowStore . addBlock (
553+ newId ,
554+ sourceBlock . type ,
555+ newName ,
556+ offsetPosition ,
557+ sourceBlock . data ? JSON . parse ( JSON . stringify ( sourceBlock . data ) ) : { } ,
558+ sourceBlock . data ?. parentId ,
559+ sourceBlock . data ?. extent
560+ )
561+
562+ // Copy subblock values to the new block
563+ const activeWorkflowId = useWorkflowRegistry . getState ( ) . activeWorkflowId
564+ if ( activeWorkflowId ) {
565+ const subBlockValues =
566+ useSubBlockStore . getState ( ) . workflowValues [ activeWorkflowId ] ?. [ sourceId ] || { }
567+ useSubBlockStore . setState ( ( state ) => ( {
568+ workflowValues : {
569+ ...state . workflowValues ,
570+ [ activeWorkflowId ] : {
571+ ...state . workflowValues [ activeWorkflowId ] ,
572+ [ newId ] : JSON . parse ( JSON . stringify ( subBlockValues ) ) ,
573+ } ,
574+ } ,
575+ } ) )
576+ }
577+
578+ // Then broadcast to other clients
579+ if ( ! isApplyingRemoteChange . current ) {
580+ emitWorkflowOperation ( 'duplicate' , 'block' , duplicatedBlockData )
581+ }
582+ } ,
583+ [ workflowStore , emitWorkflowOperation ]
584+ )
585+
472586 const collaborativeAddEdge = useCallback (
473587 ( edge : Edge ) => {
474588 // Apply locally first
@@ -780,6 +894,8 @@ export function useCollaborativeWorkflow() {
780894 collaborativeUpdateParentId,
781895 collaborativeToggleBlockWide,
782896 collaborativeToggleBlockAdvancedMode,
897+ collaborativeToggleBlockHandles,
898+ collaborativeDuplicateBlock,
783899 collaborativeAddEdge,
784900 collaborativeRemoveEdge,
785901 collaborativeSetSubblockValue,
0 commit comments