Skip to content

Commit 37c13c8

Browse files
committed
improvement(preview): ui/ux
1 parent 7f312cb commit 37c13c8

File tree

15 files changed

+588
-349
lines changed

15 files changed

+588
-349
lines changed

apps/sim/app/templates/[id]/template.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,6 @@ export default function TemplateDetails({ isWorkspaceContext = false }: Template
332332
return (
333333
<WorkflowPreview
334334
workflowState={template.state}
335-
showSubBlocks={true}
336335
height='100%'
337336
width='100%'
338337
isPannable={true}

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,6 @@ function TemplateCardInner({
204204
{normalizedState && isInView ? (
205205
<WorkflowPreview
206206
workflowState={normalizedState}
207-
showSubBlocks={false}
208207
height={180}
209208
width='100%'
210209
isPannable={false}

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

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { redactApiKeys } from '@/lib/core/security/redaction'
1717
import { cn } from '@/lib/core/utils/cn'
1818
import {
1919
BlockDetailsSidebar,
20+
getLeftmostBlockId,
2021
WorkflowPreview,
2122
} from '@/app/workspace/[workspaceId]/w/components/preview'
2223
import { useExecutionSnapshot } from '@/hooks/queries/logs'
@@ -70,6 +71,7 @@ export function ExecutionSnapshot({
7071
}: ExecutionSnapshotProps) {
7172
const { data, isLoading, error } = useExecutionSnapshot(executionId)
7273
const [pinnedBlockId, setPinnedBlockId] = useState<string | null>(null)
74+
const autoSelectedForExecutionRef = useRef<string | null>(null)
7375

7476
const [isMenuOpen, setIsMenuOpen] = useState(false)
7577
const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 })
@@ -146,12 +148,21 @@ export function ExecutionSnapshot({
146148
return blockExecutionMap
147149
}, [traceSpans])
148150

149-
useEffect(() => {
150-
setPinnedBlockId(null)
151-
}, [executionId])
152-
153151
const workflowState = data?.workflowState as WorkflowState | undefined
154152

153+
// Auto-select the leftmost block once when data loads for a new executionId
154+
useEffect(() => {
155+
if (
156+
workflowState &&
157+
!isMigratedWorkflowState(workflowState) &&
158+
autoSelectedForExecutionRef.current !== executionId
159+
) {
160+
autoSelectedForExecutionRef.current = executionId
161+
const leftmostId = getLeftmostBlockId(workflowState)
162+
setPinnedBlockId(leftmostId)
163+
}
164+
}, [executionId, workflowState])
165+
155166
const renderContent = () => {
156167
if (isLoading) {
157168
return (
@@ -218,23 +229,26 @@ export function ExecutionSnapshot({
218229
<div
219230
style={{ height, width }}
220231
className={cn(
221-
'flex overflow-hidden rounded-[4px] border border-[var(--border)]',
232+
'flex overflow-hidden',
233+
!isModal && 'rounded-[4px] border border-[var(--border)]',
222234
className
223235
)}
224236
>
225237
<div className='h-full flex-1' onContextMenu={handleCanvasContextMenu}>
226238
<WorkflowPreview
227239
workflowState={workflowState}
228-
showSubBlocks={true}
229240
isPannable={true}
230241
defaultPosition={{ x: 0, y: 0 }}
231242
defaultZoom={0.8}
232243
onNodeClick={(blockId) => {
233-
setPinnedBlockId((prev) => (prev === blockId ? null : blockId))
244+
setPinnedBlockId(blockId)
234245
}}
235246
onNodeContextMenu={handleNodeContextMenu}
247+
onPaneClick={() => setPinnedBlockId(null)}
236248
cursorStyle='pointer'
237249
executedBlocks={blockExecutions}
250+
selectedBlockId={pinnedBlockId}
251+
lightweight
238252
/>
239253
</div>
240254
{pinnedBlockId && workflowState.blocks[pinnedBlockId] && (
@@ -297,7 +311,7 @@ export function ExecutionSnapshot({
297311
<ModalContent size='full' className='flex h-[90vh] flex-col'>
298312
<ModalHeader>Workflow State</ModalHeader>
299313

300-
<ModalBody className='!p-0 min-h-0 flex-1'>{renderContent()}</ModalBody>
314+
<ModalBody className='!p-0 min-h-0 flex-1 overflow-hidden'>{renderContent()}</ModalBody>
301315
</ModalContent>
302316
</Modal>
303317
{canvasContextMenu}

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,6 @@ function TemplateCardInner({
210210
{normalizedState && isInView ? (
211211
<WorkflowPreview
212212
workflowState={normalizedState}
213-
showSubBlocks={false}
214213
height={180}
215214
width='100%'
216215
isPannable={false}

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

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { useCallback, useEffect, useMemo, useState } from 'react'
3+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
44
import { createLogger } from '@sim/logger'
55
import { Maximize2 } from 'lucide-react'
66
import {
@@ -17,6 +17,7 @@ import { Skeleton } from '@/components/ui'
1717
import type { WorkflowDeploymentVersionResponse } from '@/lib/workflows/persistence/utils'
1818
import {
1919
BlockDetailsSidebar,
20+
getLeftmostBlockId,
2021
WorkflowPreview,
2122
} from '@/app/workspace/[workspaceId]/w/components/preview'
2223
import { useDeploymentVersionState, useRevertToVersion } from '@/hooks/queries/workflows'
@@ -57,6 +58,7 @@ export function GeneralDeploy({
5758
const [showPromoteDialog, setShowPromoteDialog] = useState(false)
5859
const [showExpandedPreview, setShowExpandedPreview] = useState(false)
5960
const [expandedSelectedBlockId, setExpandedSelectedBlockId] = useState<string | null>(null)
61+
const hasAutoSelectedRef = useRef(false)
6062
const [versionToLoad, setVersionToLoad] = useState<number | null>(null)
6163
const [versionToPromote, setVersionToPromote] = useState<number | null>(null)
6264

@@ -131,6 +133,19 @@ export function GeneralDeploy({
131133
const hasDeployedData = deployedState && Object.keys(deployedState.blocks || {}).length > 0
132134
const showLoadingSkeleton = isLoadingDeployedState && !hasDeployedData
133135

136+
// Auto-select the leftmost block once when expanded preview opens
137+
useEffect(() => {
138+
if (showExpandedPreview && workflowToShow && !hasAutoSelectedRef.current) {
139+
hasAutoSelectedRef.current = true
140+
const leftmostId = getLeftmostBlockId(workflowToShow)
141+
setExpandedSelectedBlockId(leftmostId)
142+
}
143+
// Reset when modal closes
144+
if (!showExpandedPreview) {
145+
hasAutoSelectedRef.current = false
146+
}
147+
}, [showExpandedPreview, workflowToShow])
148+
134149
if (showLoadingSkeleton) {
135150
return (
136151
<div className='space-y-[12px]'>
@@ -186,36 +201,36 @@ export function GeneralDeploy({
186201
</div>
187202

188203
<div
189-
className='[&_*]:!cursor-default relative h-[260px] w-full cursor-default overflow-hidden rounded-[4px] border border-[var(--border)]'
204+
className='relative h-[260px] w-full overflow-hidden rounded-[4px] border border-[var(--border)]'
190205
onWheelCapture={(e) => {
191206
if (e.ctrlKey || e.metaKey) return
192207
e.stopPropagation()
193208
}}
194209
>
195210
{workflowToShow ? (
196211
<>
197-
<WorkflowPreview
198-
workflowState={workflowToShow}
199-
showSubBlocks={true}
200-
height='100%'
201-
width='100%'
202-
isPannable={true}
203-
defaultPosition={{ x: 0, y: 0 }}
204-
defaultZoom={0.6}
205-
/>
212+
<div className='[&_*]:!cursor-default h-full w-full cursor-default'>
213+
<WorkflowPreview
214+
workflowState={workflowToShow}
215+
height='100%'
216+
width='100%'
217+
isPannable={true}
218+
defaultPosition={{ x: 0, y: 0 }}
219+
defaultZoom={0.6}
220+
/>
221+
</div>
206222
<Tooltip.Root>
207223
<Tooltip.Trigger asChild>
208224
<Button
209225
type='button'
210226
variant='default'
211-
size='sm'
212227
onClick={() => setShowExpandedPreview(true)}
213-
className='absolute top-[8px] right-[8px] z-10'
228+
className='absolute right-[8px] bottom-[8px] z-10 h-[28px] w-[28px] cursor-pointer border border-[var(--border)] bg-transparent p-0 backdrop-blur-sm hover:bg-[var(--surface-3)]'
214229
>
215230
<Maximize2 className='h-[14px] w-[14px]' />
216231
</Button>
217232
</Tooltip.Trigger>
218-
<Tooltip.Content side='bottom'>Expand preview</Tooltip.Content>
233+
<Tooltip.Content side='top'>See preview</Tooltip.Content>
219234
</Tooltip.Root>
220235
</>
221236
) : (
@@ -316,16 +331,15 @@ export function GeneralDeploy({
316331
<div className='h-full flex-1'>
317332
<WorkflowPreview
318333
workflowState={workflowToShow}
319-
showSubBlocks={true}
320334
isPannable={true}
321335
defaultPosition={{ x: 0, y: 0 }}
322336
defaultZoom={0.6}
323337
onNodeClick={(blockId) => {
324-
setExpandedSelectedBlockId(
325-
expandedSelectedBlockId === blockId ? null : blockId
326-
)
338+
setExpandedSelectedBlockId(blockId)
327339
}}
328-
cursorStyle='pointer'
340+
onPaneClick={() => setExpandedSelectedBlockId(null)}
341+
selectedBlockId={expandedSelectedBlockId}
342+
lightweight
329343
/>
330344
</div>
331345
{expandedSelectedBlockId && workflowToShow.blocks?.[expandedSelectedBlockId] && (

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,6 @@ const OGCaptureContainer = forwardRef<HTMLDivElement>((_, ref) => {
488488
>
489489
<WorkflowPreview
490490
workflowState={workflowState}
491-
showSubBlocks={false}
492491
height='100%'
493492
width='100%'
494493
isPannable={false}
@@ -529,7 +528,6 @@ function TemplatePreviewContent({ existingTemplate }: TemplatePreviewContentProp
529528
<WorkflowPreview
530529
key={`template-preview-${existingTemplate.id}`}
531530
workflowState={workflowState}
532-
showSubBlocks={true}
533531
height='100%'
534532
width='100%'
535533
isPannable={true}

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ export interface WorkflowBlockProps {
1010
isActive?: boolean
1111
isPending?: boolean
1212
isPreview?: boolean
13+
/** Whether this block is selected in preview mode */
14+
isPreviewSelected?: boolean
1315
subBlockValues?: Record<string, any>
1416
blockState?: any
1517
}

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export function shouldSkipBlockRender(
3232
prevProps.data.isActive === nextProps.data.isActive &&
3333
prevProps.data.isPending === nextProps.data.isPending &&
3434
prevProps.data.isPreview === nextProps.data.isPreview &&
35+
prevProps.data.isPreviewSelected === nextProps.data.isPreviewSelected &&
3536
prevProps.data.config === nextProps.data.config &&
3637
prevProps.data.subBlockValues === nextProps.data.subBlockValues &&
3738
prevProps.data.blockState === nextProps.data.blockState &&

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-edge/workflow-edge.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ const WorkflowEdgeComponent = ({
9595
} else if (edgeDiffStatus === 'new') {
9696
color = 'var(--brand-tertiary)'
9797
} else if (edgeRunStatus === 'success') {
98-
color = 'var(--border-success)'
98+
// Use green for preview mode, default for canvas execution
99+
color = previewExecutionStatus ? 'var(--brand-tertiary-2)' : 'var(--border-success)'
99100
} else if (edgeRunStatus === 'error') {
100101
color = 'var(--text-error)'
101102
}
@@ -117,7 +118,7 @@ const WorkflowEdgeComponent = ({
117118
strokeDasharray: edgeDiffStatus === 'deleted' ? '10,5' : undefined,
118119
opacity,
119120
}
120-
}, [style, edgeDiffStatus, isSelected, isErrorEdge, edgeRunStatus])
121+
}, [style, edgeDiffStatus, isSelected, isErrorEdge, edgeRunStatus, previewExecutionStatus])
121122

122123
return (
123124
<>

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-block-visual.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,14 @@ interface UseBlockVisualProps {
2222
/**
2323
* Provides visual state and interaction handlers for workflow blocks.
2424
* Computes ring styling based on execution, diff, deletion, and run path states.
25-
* In preview mode, all interactive and execution-related visual states are disabled.
25+
* In preview mode, uses isPreviewSelected for selection highlighting.
2626
*
2727
* @param props - The hook properties
2828
* @returns Visual state, click handler, and ring styling for the block
2929
*/
3030
export function useBlockVisual({ blockId, data, isPending = false }: UseBlockVisualProps) {
3131
const isPreview = data.isPreview ?? false
32+
const isPreviewSelected = data.isPreviewSelected ?? false
3233

3334
const currentWorkflow = useCurrentWorkflow()
3435
const activeWorkflowId = useWorkflowRegistry((state) => state.activeWorkflowId)
@@ -40,7 +41,8 @@ export function useBlockVisual({ blockId, data, isPending = false }: UseBlockVis
4041
isDeletedBlock,
4142
} = useBlockState(blockId, currentWorkflow, data)
4243

43-
const isActive = isPreview ? false : blockIsActive
44+
// In preview mode, use isPreviewSelected for selection state
45+
const isActive = isPreview ? isPreviewSelected : blockIsActive
4446

4547
const lastRunPath = useExecutionStore((state) => state.lastRunPath)
4648
const runPathStatus = isPreview ? undefined : lastRunPath.get(blockId)
@@ -61,8 +63,9 @@ export function useBlockVisual({ blockId, data, isPending = false }: UseBlockVis
6163
isDeletedBlock: isPreview ? false : isDeletedBlock,
6264
diffStatus: isPreview ? undefined : diffStatus,
6365
runPathStatus,
66+
isPreviewSelection: isPreview && isPreviewSelected,
6467
}),
65-
[isActive, isPending, isDeletedBlock, diffStatus, runPathStatus, isPreview]
68+
[isActive, isPending, isDeletedBlock, diffStatus, runPathStatus, isPreview, isPreviewSelected]
6669
)
6770

6871
return {

0 commit comments

Comments
 (0)