11'use client'
22
33import { useCallback , useEffect , useMemo , useRef , useState } from 'react'
4- import { ArrowDown , ArrowUp , ChevronDown as ChevronDownIcon , ChevronUp , X } from 'lucide-react'
4+ import {
5+ ArrowDown ,
6+ ArrowUp ,
7+ ChevronDown as ChevronDownIcon ,
8+ ChevronUp ,
9+ RepeatIcon ,
10+ SplitIcon ,
11+ X ,
12+ } from 'lucide-react'
513import { ReactFlowProvider } from 'reactflow'
6- import { Badge , Button , ChevronDown , Code , Input } from '@/components/emcn'
14+ import { Badge , Button , ChevronDown , Code , Combobox , Input , Label } from '@/components/emcn'
715import { cn } from '@/lib/core/utils/cn'
816import { extractReferencePrefixes } from '@/lib/workflows/sanitization/references'
917import { SnapshotContextMenu } from '@/app/workspace/[workspaceId]/logs/components/log-details/components/execution-snapshot/components'
@@ -14,7 +22,7 @@ import type { BlockConfig, BlockIcon, SubBlockConfig } from '@/blocks/types'
1422import { normalizeName } from '@/executor/constants'
1523import { navigatePath } from '@/executor/variables/resolvers/reference'
1624import { useCodeViewerFeatures } from '@/hooks/use-code-viewer'
17- import type { BlockState } from '@/stores/workflows/workflow/types'
25+ import type { BlockState , Loop , Parallel } from '@/stores/workflows/workflow/types'
1826
1927/**
2028 * Evaluate whether a subblock's condition is met based on current values.
@@ -547,6 +555,165 @@ function IconComponent({
547555 return < Icon className = { className } />
548556}
549557
558+ /**
559+ * Configuration for subflow types (loop and parallel) - matches use-subflow-editor.ts
560+ */
561+ const SUBFLOW_CONFIG = {
562+ loop : {
563+ typeLabels : {
564+ for : 'For Loop' ,
565+ forEach : 'For Each' ,
566+ while : 'While Loop' ,
567+ doWhile : 'Do While Loop' ,
568+ } ,
569+ maxIterations : 1000 ,
570+ } ,
571+ parallel : {
572+ typeLabels : {
573+ count : 'Parallel Count' ,
574+ collection : 'Parallel Each' ,
575+ } ,
576+ maxIterations : 20 ,
577+ } ,
578+ } as const
579+
580+ interface SubflowConfigDisplayProps {
581+ block : BlockState
582+ loop ?: Loop
583+ parallel ?: Parallel
584+ }
585+
586+ /**
587+ * Display subflow (loop/parallel) configuration in preview mode.
588+ * Matches the exact UI structure of SubflowEditor.
589+ */
590+ function SubflowConfigDisplay ( { block, loop, parallel } : SubflowConfigDisplayProps ) {
591+ const isLoop = block . type === 'loop'
592+ const config = isLoop ? SUBFLOW_CONFIG . loop : SUBFLOW_CONFIG . parallel
593+
594+ // Determine current type
595+ const currentType = isLoop
596+ ? loop ?. loopType || ( block . data ?. loopType as string ) || 'for'
597+ : parallel ?. parallelType || ( block . data ?. parallelType as string ) || 'count'
598+
599+ // Build type options for combobox - matches SubflowEditor
600+ const typeOptions = Object . entries ( config . typeLabels ) . map ( ( [ value , label ] ) => ( {
601+ value,
602+ label,
603+ } ) )
604+
605+ // Determine mode
606+ const isCountMode = currentType === 'for' || currentType === 'count'
607+ const isConditionMode = currentType === 'while' || currentType === 'doWhile'
608+
609+ // Get iterations value
610+ const iterations = isLoop
611+ ? ( loop ?. iterations ?? ( block . data ?. count as number ) ?? 5 )
612+ : ( parallel ?. count ?? ( block . data ?. count as number ) ?? 1 )
613+
614+ // Get collection/condition value
615+ const getEditorValue = ( ) : string => {
616+ if ( isConditionMode && isLoop ) {
617+ if ( currentType === 'while' ) {
618+ return loop ?. whileCondition || ( block . data ?. whileCondition as string ) || ''
619+ }
620+ return loop ?. doWhileCondition || ( block . data ?. doWhileCondition as string ) || ''
621+ }
622+
623+ if ( isLoop ) {
624+ const items = loop ?. forEachItems ?? block . data ?. collection
625+ return typeof items === 'string' ? items : JSON . stringify ( items ) || ''
626+ }
627+
628+ const distribution = parallel ?. distribution ?? block . data ?. collection
629+ return typeof distribution === 'string' ? distribution : JSON . stringify ( distribution ) || ''
630+ }
631+
632+ const editorValue = getEditorValue ( )
633+
634+ // Get label for configuration field - matches SubflowEditor exactly
635+ const getConfigLabel = ( ) : string => {
636+ if ( isCountMode ) {
637+ return `${ isLoop ? 'Loop' : 'Parallel' } Iterations`
638+ }
639+ if ( isConditionMode ) {
640+ return 'While Condition'
641+ }
642+ return `${ isLoop ? 'Collection' : 'Parallel' } Items`
643+ }
644+
645+ return (
646+ < div className = 'flex-1 overflow-y-auto overflow-x-hidden pt-[5px] pb-[8px]' >
647+ { /* Type Selection - matches SubflowEditor */ }
648+ < div >
649+ < Label className = 'mb-[6.5px] block pl-[2px] font-medium text-[13px] text-[var(--text-primary)]' >
650+ { isLoop ? 'Loop Type' : 'Parallel Type' }
651+ </ Label >
652+ < Combobox
653+ options = { typeOptions }
654+ value = { currentType }
655+ onChange = { ( ) => { } }
656+ disabled
657+ placeholder = 'Select type...'
658+ />
659+ </ div >
660+
661+ { /* Dashed Line Separator - matches SubflowEditor */ }
662+ < div className = 'px-[2px] pt-[16px] pb-[10px]' >
663+ < div
664+ className = 'h-[1.25px]'
665+ style = { {
666+ backgroundImage :
667+ 'repeating-linear-gradient(to right, var(--border) 0px, var(--border) 6px, transparent 6px, transparent 12px)' ,
668+ } }
669+ />
670+ </ div >
671+
672+ { /* Configuration - matches SubflowEditor */ }
673+ < div >
674+ < Label className = 'mb-[6.5px] block pl-[2px] font-medium text-[13px] text-[var(--text-primary)]' >
675+ { getConfigLabel ( ) }
676+ </ Label >
677+
678+ { isCountMode ? (
679+ < div >
680+ < Input
681+ type = 'text'
682+ value = { iterations . toString ( ) }
683+ onChange = { ( ) => { } }
684+ disabled
685+ className = 'mb-[4px]'
686+ />
687+ < div className = 'text-[10px] text-muted-foreground' >
688+ Enter a number between 1 and { config . maxIterations }
689+ </ div >
690+ </ div >
691+ ) : (
692+ < div className = 'relative' >
693+ < Code . Container >
694+ < Code . Content >
695+ < Code . Placeholder gutterWidth = { 0 } show = { editorValue . length === 0 } >
696+ { isConditionMode ? '<counter.value> < 10' : "['item1', 'item2', 'item3']" }
697+ </ Code . Placeholder >
698+ < div
699+ className = 'min-h-[24px] whitespace-pre-wrap break-all px-[12px] py-[8px] font-mono text-[13px] text-[var(--text-secondary)]'
700+ style = { { pointerEvents : 'none' } }
701+ >
702+ { editorValue || (
703+ < span className = 'text-[var(--text-tertiary)]' >
704+ { isConditionMode ? '<counter.value> < 10' : "['item1', 'item2', 'item3']" }
705+ </ span >
706+ ) }
707+ </ div >
708+ </ Code . Content >
709+ </ Code . Container >
710+ </ div >
711+ ) }
712+ </ div >
713+ </ div >
714+ )
715+ }
716+
550717interface ExecutionData {
551718 input ?: unknown
552719 output ?: unknown
@@ -570,6 +737,10 @@ interface BlockDetailsSidebarProps {
570737 workflowBlocks ?: Record < string , BlockState >
571738 /** Workflow variables for resolving variable references */
572739 workflowVariables ?: Record < string , WorkflowVariable >
740+ /** Loop configurations for subflow blocks */
741+ loops ?: Record < string , Loop >
742+ /** Parallel configurations for subflow blocks */
743+ parallels ?: Record < string , Parallel >
573744 /** When true, shows "Not Executed" badge if no executionData is provided */
574745 isExecutionMode ?: boolean
575746 /** Optional close handler - if not provided, no close button is shown */
@@ -600,6 +771,8 @@ function BlockDetailsSidebarContent({
600771 allBlockExecutions,
601772 workflowBlocks,
602773 workflowVariables,
774+ loops,
775+ parallels,
603776 isExecutionMode = false ,
604777 onClose,
605778} : BlockDetailsSidebarProps ) {
@@ -868,6 +1041,71 @@ function BlockDetailsSidebarContent({
8681041 } )
8691042 } , [ extractedRefs . envVars ] )
8701043
1044+ // Check if this is a subflow block (loop or parallel)
1045+ const isSubflow = block . type === 'loop' || block . type === 'parallel'
1046+ const loopConfig = block . type === 'loop' ? loops ?. [ block . id ] : undefined
1047+ const parallelConfig = block . type === 'parallel' ? parallels ?. [ block . id ] : undefined
1048+
1049+ // Handle subflow blocks
1050+ if ( isSubflow ) {
1051+ const isLoop = block . type === 'loop'
1052+ const SubflowIcon = isLoop ? RepeatIcon : SplitIcon
1053+ const subflowBgColor = isLoop ? '#2FB3FF' : '#FEE12B'
1054+ const subflowName = block . name || ( isLoop ? 'Loop' : 'Parallel' )
1055+
1056+ return (
1057+ < div className = 'relative flex h-full w-80 flex-col overflow-hidden border-[var(--border)] border-l bg-[var(--surface-1)]' >
1058+ { /* Header - styled like subflow header */ }
1059+ < div className = 'mx-[-1px] flex flex-shrink-0 items-center gap-[8px] rounded-b-[4px] border-[var(--border)] border-x border-b bg-[var(--surface-4)] px-[12px] py-[6px]' >
1060+ < div
1061+ className = 'flex h-[18px] w-[18px] flex-shrink-0 items-center justify-center rounded-[4px]'
1062+ style = { { backgroundColor : subflowBgColor } }
1063+ >
1064+ < SubflowIcon className = 'h-[12px] w-[12px] text-white' />
1065+ </ div >
1066+ < span className = 'min-w-0 flex-1 truncate font-medium text-[14px] text-[var(--text-primary)]' >
1067+ { subflowName }
1068+ </ span >
1069+ { onClose && (
1070+ < Button variant = 'ghost' className = '!p-[4px] flex-shrink-0' onClick = { onClose } >
1071+ < X className = 'h-[14px] w-[14px]' />
1072+ </ Button >
1073+ ) }
1074+ </ div >
1075+
1076+ { /* Subflow Configuration */ }
1077+ < div className = 'flex flex-1 flex-col overflow-hidden pt-[0px]' >
1078+ < div className = 'flex-1 overflow-y-auto overflow-x-hidden' >
1079+ < div className = 'readonly-preview px-[8px]' >
1080+ { /* CSS override to show full opacity and prevent interaction instead of dimmed disabled state */ }
1081+ < style > { `
1082+ .readonly-preview,
1083+ .readonly-preview * {
1084+ cursor: default !important;
1085+ }
1086+ .readonly-preview [disabled],
1087+ .readonly-preview [data-disabled],
1088+ .readonly-preview input,
1089+ .readonly-preview textarea,
1090+ .readonly-preview [role="combobox"],
1091+ .readonly-preview [role="slider"],
1092+ .readonly-preview [role="switch"],
1093+ .readonly-preview [role="checkbox"] {
1094+ opacity: 1 !important;
1095+ pointer-events: none;
1096+ }
1097+ .readonly-preview .opacity-50 {
1098+ opacity: 1 !important;
1099+ }
1100+ ` } </ style >
1101+ < SubflowConfigDisplay block = { block } loop = { loopConfig } parallel = { parallelConfig } />
1102+ </ div >
1103+ </ div >
1104+ </ div >
1105+ </ div >
1106+ )
1107+ }
1108+
8711109 if ( ! blockConfig ) {
8721110 return (
8731111 < div className = 'flex h-full w-80 flex-col overflow-hidden border-[var(--border)] border-l bg-[var(--surface-1)]' >
0 commit comments