Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/sim/app/api/users/me/settings/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const SettingsSchema = z.object({
superUserModeEnabled: z.boolean().optional(),
errorNotificationsEnabled: z.boolean().optional(),
snapToGridSize: z.number().min(0).max(50).optional(),
showActionBar: z.boolean().optional(),
})

const defaultSettings = {
Expand All @@ -39,6 +40,7 @@ const defaultSettings = {
superUserModeEnabled: false,
errorNotificationsEnabled: true,
snapToGridSize: 0,
showActionBar: true,
}

export async function GET() {
Expand Down Expand Up @@ -73,6 +75,7 @@ export async function GET() {
superUserModeEnabled: userSettings.superUserModeEnabled ?? true,
errorNotificationsEnabled: userSettings.errorNotificationsEnabled ?? true,
snapToGridSize: userSettings.snapToGridSize ?? 0,
showActionBar: userSettings.showActionBar ?? true,
},
},
{ status: 200 }
Expand Down
6 changes: 6 additions & 0 deletions apps/sim/app/playground/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@ import {
Combobox,
Connections,
Copy,
Cursor,
DatePicker,
DocumentAttachment,
Duplicate,
Expand,
Eye,
FolderCode,
FolderPlus,
Hand,
HexSimple,
Input,
Key as KeyIcon,
Expand Down Expand Up @@ -979,11 +982,14 @@ export default function PlaygroundPage() {
{ Icon: ChevronDown, name: 'ChevronDown' },
{ Icon: Connections, name: 'Connections' },
{ Icon: Copy, name: 'Copy' },
{ Icon: Cursor, name: 'Cursor' },
{ Icon: DocumentAttachment, name: 'DocumentAttachment' },
{ Icon: Duplicate, name: 'Duplicate' },
{ Icon: Expand, name: 'Expand' },
{ Icon: Eye, name: 'Eye' },
{ Icon: FolderCode, name: 'FolderCode' },
{ Icon: FolderPlus, name: 'FolderPlus' },
{ Icon: Hand, name: 'Hand' },
{ Icon: HexSimple, name: 'HexSimple' },
{ Icon: KeyIcon, name: 'Key' },
{ Icon: Layout, name: 'Layout' },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
'use client'

import { useRef, useState } from 'react'
import { createLogger } from '@sim/logger'
import { useReactFlow } from 'reactflow'
import {
Button,
Cursor,
Expand,
Hand,
Popover,
PopoverAnchor,
PopoverContent,
PopoverItem,
Redo,
Tooltip,
Undo,
ZoomIn,
ZoomOut,
} from '@/components/emcn'
import { useSession } from '@/lib/auth/auth-client'
import { useUpdateGeneralSetting } from '@/hooks/queries/general-settings'
import { useCanvasViewport } from '@/hooks/use-canvas-viewport'
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
import { useCanvasModeStore } from '@/stores/canvas-mode'
import { useGeneralStore } from '@/stores/settings/general'
import { useUndoRedoStore } from '@/stores/undo-redo'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'

const logger = createLogger('ActionBar')

export function ActionBar() {
const reactFlowInstance = useReactFlow()
const { zoomIn, zoomOut } = reactFlowInstance
const { fitViewToBounds } = useCanvasViewport(reactFlowInstance)
const { mode, setMode } = useCanvasModeStore()
const { undo, redo } = useCollaborativeWorkflow()
const showActionBar = useGeneralStore((s) => s.showActionBar)
const updateSetting = useUpdateGeneralSetting()

const { activeWorkflowId } = useWorkflowRegistry()
const { data: session } = useSession()
const userId = session?.user?.id || 'unknown'
const stacks = useUndoRedoStore((s) => s.stacks)
const key = activeWorkflowId && userId ? `${activeWorkflowId}:${userId}` : ''
const stack = (key && stacks[key]) || { undo: [], redo: [] }
const canUndo = stack.undo.length > 0
const canRedo = stack.redo.length > 0

const [contextMenu, setContextMenu] = useState<{ x: number; y: number } | null>(null)
const menuRef = useRef<HTMLDivElement>(null)

const handleContextMenu = (e: React.MouseEvent) => {
e.preventDefault()
setContextMenu({ x: e.clientX, y: e.clientY })
}

const handleHide = async () => {
try {
await updateSetting.mutateAsync({ key: 'showActionBar', value: false })
} catch (error) {
logger.error('Failed to hide action bar', error)
} finally {
setContextMenu(null)
}
}

if (!showActionBar) {
return null
}

return (
<>
<div
className='fixed bottom-[calc(var(--terminal-height)+16px)] left-[calc(var(--sidebar-width)+16px)] z-10 flex h-[36px] items-center gap-[2px] rounded-[8px] border border-[var(--border)] bg-[var(--surface-1)] p-[4px] shadow-sm transition-[left,bottom] duration-100 ease-out'
onContextMenu={handleContextMenu}
>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<Button
variant={mode === 'hand' ? 'secondary' : 'ghost'}
className='h-[28px] w-[28px] p-0'
onClick={() => setMode('hand')}
>
<Hand className='h-[16px] w-[16px]' />
</Button>
</Tooltip.Trigger>
<Tooltip.Content side='top'>Hand tool</Tooltip.Content>
</Tooltip.Root>

<Tooltip.Root>
<Tooltip.Trigger asChild>
<Button
variant={mode === 'cursor' ? 'secondary' : 'ghost'}
className='h-[28px] w-[28px] p-0'
onClick={() => setMode('cursor')}
>
<Cursor className='h-[16px] w-[16px]' />
</Button>
</Tooltip.Trigger>
<Tooltip.Content side='top'>Move</Tooltip.Content>
</Tooltip.Root>

<div className='mx-[4px] h-[20px] w-[1px] bg-[var(--border)]' />

<Tooltip.Root>
<Tooltip.Trigger asChild>
<Button
variant='ghost'
className='h-[28px] w-[28px] p-0'
onClick={undo}
disabled={!canUndo}
>
<Undo className='h-[16px] w-[16px]' />
</Button>
</Tooltip.Trigger>
<Tooltip.Content side='top'>
<Tooltip.Shortcut keys='⌘Z'>Undo</Tooltip.Shortcut>
</Tooltip.Content>
</Tooltip.Root>

<Tooltip.Root>
<Tooltip.Trigger asChild>
<Button
variant='ghost'
className='h-[28px] w-[28px] p-0'
onClick={redo}
disabled={!canRedo}
>
<Redo className='h-[16px] w-[16px]' />
</Button>
</Tooltip.Trigger>
<Tooltip.Content side='top'>
<Tooltip.Shortcut keys='⌘⇧Z'>Redo</Tooltip.Shortcut>
</Tooltip.Content>
</Tooltip.Root>

<div className='mx-[4px] h-[20px] w-[1px] bg-[var(--border)]' />

<Tooltip.Root>
<Tooltip.Trigger asChild>
<Button variant='ghost' className='h-[28px] w-[28px] p-0' onClick={() => zoomOut()}>
<ZoomOut className='h-[16px] w-[16px]' />
</Button>
</Tooltip.Trigger>
<Tooltip.Content side='top'>Zoom out</Tooltip.Content>
</Tooltip.Root>

<Tooltip.Root>
<Tooltip.Trigger asChild>
<Button variant='ghost' className='h-[28px] w-[28px] p-0' onClick={() => zoomIn()}>
<ZoomIn className='h-[16px] w-[16px]' />
</Button>
</Tooltip.Trigger>
<Tooltip.Content side='top'>Zoom in</Tooltip.Content>
</Tooltip.Root>

<Tooltip.Root>
<Tooltip.Trigger asChild>
<Button
variant='ghost'
className='h-[28px] w-[28px] p-0'
onClick={() => fitViewToBounds({ padding: 0.1, duration: 300 })}
>
<Expand className='h-[16px] w-[16px]' />
</Button>
</Tooltip.Trigger>
<Tooltip.Content side='top'>Zoom to fit</Tooltip.Content>
</Tooltip.Root>
</div>

<Popover
open={contextMenu !== null}
onOpenChange={(open) => !open && setContextMenu(null)}
variant='secondary'
size='sm'
colorScheme='inverted'
>
<PopoverAnchor
style={{
position: 'fixed',
left: `${contextMenu?.x ?? 0}px`,
top: `${contextMenu?.y ?? 0}px`,
width: '1px',
height: '1px',
}}
/>
<PopoverContent ref={menuRef} align='start' side='bottom' sideOffset={4}>
<PopoverItem onClick={handleHide}>Hide canvas controls</PopoverItem>
</PopoverContent>
</Popover>
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ActionBar } from './action-bar'
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { ActionBar } from './action-bar'
export { CommandList } from './command-list/command-list'
export { Cursors } from './cursors/cursors'
export { DiffControls } from './diff-controls/diff-controls'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { createLogger } from '@sim/logger'
import { Maximize2 } from 'lucide-react'
import {
Button,
ButtonGroup,
ButtonGroupItem,
Expand,
Label,
Modal,
ModalBody,
Expand Down Expand Up @@ -222,7 +222,7 @@ export function GeneralDeploy({
onClick={() => setShowExpandedPreview(true)}
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)]'
>
<Maximize2 className='h-[14px] w-[14px]' />
<Expand className='h-[14px] w-[14px]' />
</Button>
</Tooltip.Trigger>
<Tooltip.Content side='top'>See preview</Tooltip.Content>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,3 @@ export { Copilot } from './copilot/copilot'
export { Deploy } from './deploy/deploy'
export { Editor } from './editor/editor'
export { Toolbar } from './toolbar/toolbar'
export { WorkflowControls } from './workflow-controls/workflow-controls'

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -495,9 +495,6 @@ export function Panel() {
Editor
</Button>
</div>

{/* Workflow Controls (Undo/Redo) */}
{/* <WorkflowControls /> */}
</div>

{/* Tab Content - Keep all tabs mounted but hidden to preserve state */}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createLogger } from '@sim/logger'
import { useReactFlow } from 'reactflow'
import type { AutoLayoutOptions } from '@/app/workspace/[workspaceId]/w/[workflowId]/utils/auto-layout-utils'
import { applyAutoLayoutAndUpdateStore as applyAutoLayoutStandalone } from '@/app/workspace/[workspaceId]/w/[workflowId]/utils/auto-layout-utils'
import { useCanvasViewport } from '@/hooks/use-canvas-viewport'

export type { AutoLayoutOptions }

Expand All @@ -16,7 +17,8 @@ const logger = createLogger('useAutoLayout')
* Note: This hook requires a ReactFlowProvider ancestor.
*/
export function useAutoLayout(workflowId: string | null) {
const { fitView } = useReactFlow()
const reactFlowInstance = useReactFlow()
const { fitViewToBounds } = useCanvasViewport(reactFlowInstance)

const applyAutoLayoutAndUpdateStore = useCallback(
async (options: AutoLayoutOptions = {}) => {
Expand All @@ -38,7 +40,7 @@ export function useAutoLayout(workflowId: string | null) {
if (result.success) {
logger.info('Auto layout completed successfully')
requestAnimationFrame(() => {
fitView({ padding: 0.8, duration: 600 })
fitViewToBounds({ padding: 0.15, duration: 600 })
})
} else {
logger.error('Auto layout failed:', result.error)
Expand All @@ -52,7 +54,7 @@ export function useAutoLayout(workflowId: string | null) {
error: error instanceof Error ? error.message : 'Unknown error',
}
}
}, [applyAutoLayoutAndUpdateStore, fitView])
}, [applyAutoLayoutAndUpdateStore, fitViewToBounds])

return {
applyAutoLayoutAndUpdateStore,
Expand Down
Loading