Skip to content

Commit 8ef5b5b

Browse files
committed
feat(workflow-block): preview
1 parent fb8868c commit 8ef5b5b

File tree

16 files changed

+233
-203
lines changed

16 files changed

+233
-203
lines changed

apps/sim/app/templates/components/template-card.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,6 @@ function TemplateCardInner({
207207
isPannable={false}
208208
defaultZoom={0.8}
209209
fitPadding={0.2}
210-
lightweight
211210
/>
212211
) : (
213212
<div className='h-full w-full bg-[var(--surface-4)]' />

apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/execution-snapshot/execution-snapshot.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ import {
1616
import { redactApiKeys } from '@/lib/core/security/redaction'
1717
import { cn } from '@/lib/core/utils/cn'
1818
import {
19-
BlockDetailsSidebar,
2019
getLeftmostBlockId,
20+
PreviewEditor,
2121
WorkflowPreview,
2222
} from '@/app/workspace/[workspaceId]/w/components/preview'
2323
import { useExecutionSnapshot } from '@/hooks/queries/logs'
@@ -248,11 +248,10 @@ export function ExecutionSnapshot({
248248
cursorStyle='pointer'
249249
executedBlocks={blockExecutions}
250250
selectedBlockId={pinnedBlockId}
251-
lightweight
252251
/>
253252
</div>
254253
{pinnedBlockId && workflowState.blocks[pinnedBlockId] && (
255-
<BlockDetailsSidebar
254+
<PreviewEditor
256255
block={workflowState.blocks[pinnedBlockId]}
257256
executionData={blockExecutions[pinnedBlockId]}
258257
allBlockExecutions={blockExecutions}

apps/sim/app/workspace/[workspaceId]/templates/components/template-card.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,6 @@ function TemplateCardInner({
213213
isPannable={false}
214214
defaultZoom={0.8}
215215
fitPadding={0.2}
216-
lightweight
217216
cursorStyle='pointer'
218217
/>
219218
) : (

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/general/general.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ import {
1818
import { Skeleton } from '@/components/ui'
1919
import type { WorkflowDeploymentVersionResponse } from '@/lib/workflows/persistence/utils'
2020
import {
21-
BlockDetailsSidebar,
2221
getLeftmostBlockId,
22+
PreviewEditor,
2323
WorkflowPreview,
2424
} from '@/app/workspace/[workspaceId]/w/components/preview'
2525
import { useDeploymentVersionState, useRevertToVersion } from '@/hooks/queries/workflows'
@@ -337,7 +337,7 @@ export function GeneralDeploy({
337337
/>
338338
</div>
339339
{expandedSelectedBlockId && workflowToShow.blocks?.[expandedSelectedBlockId] && (
340-
<BlockDetailsSidebar
340+
<PreviewEditor
341341
block={workflowToShow.blocks[expandedSelectedBlockId]}
342342
workflowVariables={workflowToShow.variables}
343343
loops={workflowToShow.loops}

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/template/template.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,6 @@ const OGCaptureContainer = forwardRef<HTMLDivElement>((_, ref) => {
446446
isPannable={false}
447447
defaultZoom={0.8}
448448
fitPadding={0.2}
449-
lightweight
450449
/>
451450
</div>
452451
)

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/input-mapping/input-mapping.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ import { useMemo, useRef, useState } from 'react'
22
import { Badge, Input } from '@/components/emcn'
33
import { Label } from '@/components/ui/label'
44
import { cn } from '@/lib/core/utils/cn'
5+
import { extractInputFieldsFromBlocks } from '@/lib/workflows/input-format'
56
import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text'
67
import { TagDropdown } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown'
78
import { useSubBlockInput } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-input'
89
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
910
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
10-
import { useWorkflowInputFields } from '@/hooks/queries/workflows'
11+
import { useWorkflowState } from '@/hooks/queries/workflows'
1112

1213
/**
1314
* Props for the InputMappingField component
@@ -70,7 +71,11 @@ export function InputMapping({
7071
const overlayRefs = useRef<Map<string, HTMLDivElement>>(new Map())
7172

7273
const workflowId = typeof selectedWorkflowId === 'string' ? selectedWorkflowId : undefined
73-
const { data: childInputFields = [], isLoading } = useWorkflowInputFields(workflowId)
74+
const { data: workflowState, isLoading } = useWorkflowState(workflowId)
75+
const childInputFields = useMemo(
76+
() => (workflowState?.blocks ? extractInputFieldsFromBlocks(workflowState.blocks) : []),
77+
[workflowState?.blocks]
78+
)
7479
const [collapsedFields, setCollapsedFields] = useState<Record<string, boolean>>({})
7580

7681
const valueObj: Record<string, string> = useMemo(() => {

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
type OAuthProvider,
3030
type OAuthService,
3131
} from '@/lib/oauth'
32+
import { extractInputFieldsFromBlocks } from '@/lib/workflows/input-format'
3233
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
3334
import {
3435
CheckboxList,
@@ -65,7 +66,7 @@ import { useForceRefreshMcpTools, useMcpServers, useStoredMcpTools } from '@/hoo
6566
import {
6667
useChildDeploymentStatus,
6768
useDeployChildWorkflow,
68-
useWorkflowInputFields,
69+
useWorkflowState,
6970
useWorkflows,
7071
} from '@/hooks/queries/workflows'
7172
import { usePermissionConfig } from '@/hooks/use-permission-config'
@@ -771,7 +772,11 @@ function WorkflowInputMapperSyncWrapper({
771772
disabled: boolean
772773
workflowId: string
773774
}) {
774-
const { data: inputFields = [], isLoading } = useWorkflowInputFields(workflowId)
775+
const { data: workflowState, isLoading } = useWorkflowState(workflowId)
776+
const inputFields = useMemo(
777+
() => (workflowState?.blocks ? extractInputFieldsFromBlocks(workflowState.blocks) : []),
778+
[workflowState?.blocks]
779+
)
775780

776781
const parsedValue = useMemo(() => {
777782
try {

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/subflow-editor/subflow-editor.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export function SubflowEditor({
6868
<div className='flex flex-1 flex-col overflow-hidden pt-[0px]'>
6969
{/* Subflow Editor Section */}
7070
<div ref={subBlocksRef} className='subblocks-section flex flex-1 flex-col overflow-hidden'>
71-
<div className='flex-1 overflow-y-auto overflow-x-hidden px-[8px] pt-[5px] pb-[8px]'>
71+
<div className='flex-1 overflow-y-auto overflow-x-hidden px-[8px] pt-[9px] pb-[8px]'>
7272
{/* Type Selection */}
7373
<div>
7474
<Label className='mb-[6.5px] block pl-[2px] font-medium text-[13px] text-[var(--text-primary)]'>

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/editor.tsx

Lines changed: 99 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,16 @@
22

33
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
44
import { isEqual } from 'lodash'
5-
import { BookOpen, Check, ChevronDown, ChevronUp, Pencil } from 'lucide-react'
5+
import {
6+
BookOpen,
7+
Check,
8+
ChevronDown,
9+
ChevronUp,
10+
ExternalLink,
11+
Loader2,
12+
Pencil,
13+
} from 'lucide-react'
14+
import { useParams } from 'next/navigation'
615
import { useShallow } from 'zustand/react/shallow'
716
import { Button, Tooltip } from '@/components/emcn'
817
import {
@@ -28,8 +37,10 @@ import { LoopTool } from '@/app/workspace/[workspaceId]/w/[workflowId]/component
2837
import { ParallelTool } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/subflows/parallel/parallel-config'
2938
import { getSubBlockStableKey } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/utils'
3039
import { useCurrentWorkflow } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks'
40+
import { WorkflowPreview } from '@/app/workspace/[workspaceId]/w/components/preview'
3141
import { getBlock } from '@/blocks/registry'
3242
import type { SubBlockType } from '@/blocks/types'
43+
import { useWorkflowState } from '@/hooks/queries/workflows'
3344
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
3445
import { usePanelEditorStore } from '@/stores/panel'
3546
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
@@ -84,6 +95,14 @@ export function Editor() {
8495
// Get subflow display properties from configs
8596
const subflowConfig = isSubflow ? (currentBlock.type === 'loop' ? LoopTool : ParallelTool) : null
8697

98+
// Check if selected block is a workflow block
99+
const isWorkflowBlock =
100+
currentBlock && (currentBlock.type === 'workflow' || currentBlock.type === 'workflow_input')
101+
102+
// Get workspace ID from params
103+
const params = useParams()
104+
const workspaceId = params.workspaceId as string
105+
87106
// Refs for resize functionality
88107
const subBlocksRef = useRef<HTMLDivElement>(null)
89108

@@ -252,11 +271,11 @@ export function Editor() {
252271

253272
// Trigger rename mode when signaled from context menu
254273
useEffect(() => {
255-
if (shouldFocusRename && currentBlock && !isSubflow) {
274+
if (shouldFocusRename && currentBlock) {
256275
handleStartRename()
257276
setShouldFocusRename(false)
258277
}
259-
}, [shouldFocusRename, currentBlock, isSubflow, handleStartRename, setShouldFocusRename])
278+
}, [shouldFocusRename, currentBlock, handleStartRename, setShouldFocusRename])
260279

261280
/**
262281
* Handles opening documentation link in a new secure tab.
@@ -268,6 +287,22 @@ export function Editor() {
268287
}
269288
}
270289

290+
// Get child workflow ID for workflow blocks
291+
const childWorkflowId = isWorkflowBlock ? blockSubBlockValues?.workflowId : null
292+
293+
// Fetch child workflow state for preview (only for workflow blocks with a selected workflow)
294+
const { data: childWorkflowState, isLoading: isLoadingChildWorkflow } =
295+
useWorkflowState(childWorkflowId)
296+
297+
/**
298+
* Handles opening the child workflow in a new tab.
299+
*/
300+
const handleOpenChildWorkflow = useCallback(() => {
301+
if (childWorkflowId && workspaceId) {
302+
window.open(`/workspace/${workspaceId}/w/${childWorkflowId}`, '_blank', 'noopener,noreferrer')
303+
}
304+
}, [childWorkflowId, workspaceId])
305+
271306
// Determine if connections are at minimum height (collapsed state)
272307
const isConnectionsAtMinHeight = connectionsHeight <= 35
273308

@@ -320,7 +355,7 @@ export function Editor() {
320355
</div>
321356
<div className='flex shrink-0 items-center gap-[8px]'>
322357
{/* Rename button */}
323-
{currentBlock && !isSubflow && (
358+
{currentBlock && (
324359
<Tooltip.Root>
325360
<Tooltip.Trigger asChild>
326361
<Button
@@ -406,7 +441,66 @@ export function Editor() {
406441
className='subblocks-section flex flex-1 flex-col overflow-hidden'
407442
>
408443
<div className='flex-1 overflow-y-auto overflow-x-hidden px-[8px] pt-[12px] pb-[8px] [overflow-anchor:none]'>
409-
{subBlocks.length === 0 ? (
444+
{/* Workflow Preview - only for workflow blocks with a selected child workflow */}
445+
{isWorkflowBlock && childWorkflowId && (
446+
<>
447+
<div className='subblock-content flex flex-col gap-[9.5px]'>
448+
<div className='pl-[2px] font-medium text-[13px] text-[var(--text-primary)] leading-none'>
449+
Workflow Preview
450+
</div>
451+
<div className='relative h-[160px] overflow-hidden rounded-[4px] border border-[var(--border)]'>
452+
{isLoadingChildWorkflow ? (
453+
<div className='flex h-full items-center justify-center bg-[var(--surface-3)]'>
454+
<Loader2 className='h-5 w-5 animate-spin text-[var(--text-tertiary)]' />
455+
</div>
456+
) : childWorkflowState ? (
457+
<>
458+
<div className='[&_*:active]:!cursor-grabbing [&_*]:!cursor-grab [&_.react-flow__handle]:!hidden h-full w-full'>
459+
<WorkflowPreview
460+
workflowState={childWorkflowState}
461+
height={160}
462+
width='100%'
463+
isPannable={true}
464+
defaultZoom={0.6}
465+
fitPadding={0.15}
466+
cursorStyle='grab'
467+
/>
468+
</div>
469+
<Tooltip.Root>
470+
<Tooltip.Trigger asChild>
471+
<Button
472+
type='button'
473+
variant='ghost'
474+
onClick={handleOpenChildWorkflow}
475+
className='absolute right-[6px] bottom-[6px] z-10 h-[24px] w-[24px] cursor-pointer border border-[var(--border)] bg-[var(--surface-2)] p-0 hover:bg-[var(--surface-4)]'
476+
>
477+
<ExternalLink className='h-[12px] w-[12px]' />
478+
</Button>
479+
</Tooltip.Trigger>
480+
<Tooltip.Content side='top'>Open workflow</Tooltip.Content>
481+
</Tooltip.Root>
482+
</>
483+
) : (
484+
<div className='flex h-full items-center justify-center bg-[var(--surface-3)]'>
485+
<span className='text-[13px] text-[var(--text-tertiary)]'>
486+
Unable to load preview
487+
</span>
488+
</div>
489+
)}
490+
</div>
491+
</div>
492+
<div className='subblock-divider px-[2px] pt-[16px] pb-[13px]'>
493+
<div
494+
className='h-[1.25px]'
495+
style={{
496+
backgroundImage:
497+
'repeating-linear-gradient(to right, var(--border) 0px, var(--border) 6px, transparent 6px, transparent 12px)',
498+
}}
499+
/>
500+
</div>
501+
</>
502+
)}
503+
{subBlocks.length === 0 && !isWorkflowBlock ? (
410504
<div className='flex h-full items-center justify-center text-center text-[#8D8D8D] text-[13px]'>
411505
This block has no subblocks
412506
</div>

apps/sim/app/workspace/[workspaceId]/w/components/preview/components/block.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,9 @@ interface WorkflowPreviewBlockData {
2121
}
2222

2323
/**
24-
* Lightweight block component for workflow previews.
25-
* Renders block header, dummy subblocks skeleton, and handles.
26-
* Respects horizontalHandles and enabled state from workflow.
27-
* No heavy hooks, store subscriptions, or interactive features.
28-
* Used in template cards and other preview contexts for performance.
24+
* Preview block component for workflow visualization.
25+
* Renders block header, subblocks skeleton, and handles without
26+
* hooks, store subscriptions, or interactive features.
2927
*/
3028
function WorkflowPreviewBlockInner({ data }: NodeProps<WorkflowPreviewBlockData>) {
3129
const {

0 commit comments

Comments
 (0)