Skip to content

Commit 679b384

Browse files
committed
feat(workflow-block): preview
1 parent 900d3ef commit 679b384

File tree

16 files changed

+233
-206
lines changed

16 files changed

+233
-206
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 { useStoreWithEqualityFn } from 'zustand/traditional'
817
import { Button, Tooltip } from '@/components/emcn'
@@ -29,8 +38,10 @@ import { LoopTool } from '@/app/workspace/[workspaceId]/w/[workflowId]/component
2938
import { ParallelTool } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/subflows/parallel/parallel-config'
3039
import { getSubBlockStableKey } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/utils'
3140
import { useCurrentWorkflow } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks'
41+
import { WorkflowPreview } from '@/app/workspace/[workspaceId]/w/components/preview'
3242
import { getBlock } from '@/blocks/registry'
3343
import type { SubBlockType } from '@/blocks/types'
44+
import { useWorkflowState } from '@/hooks/queries/workflows'
3445
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
3546
import { usePanelEditorStore } from '@/stores/panel'
3647
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
@@ -85,6 +96,14 @@ export function Editor() {
8596
// Get subflow display properties from configs
8697
const subflowConfig = isSubflow ? (currentBlock.type === 'loop' ? LoopTool : ParallelTool) : null
8798

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

@@ -254,11 +273,11 @@ export function Editor() {
254273

255274
// Trigger rename mode when signaled from context menu
256275
useEffect(() => {
257-
if (shouldFocusRename && currentBlock && !isSubflow) {
276+
if (shouldFocusRename && currentBlock) {
258277
handleStartRename()
259278
setShouldFocusRename(false)
260279
}
261-
}, [shouldFocusRename, currentBlock, isSubflow, handleStartRename, setShouldFocusRename])
280+
}, [shouldFocusRename, currentBlock, handleStartRename, setShouldFocusRename])
262281

263282
/**
264283
* Handles opening documentation link in a new secure tab.
@@ -270,6 +289,22 @@ export function Editor() {
270289
}
271290
}
272291

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

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