Skip to content

Commit 1cc489e

Browse files
feat(workflow-controls): added action bar for workflow controls (#2767)
* feat(workflow-controls): added action bar for picker/hand/undo/redo/zoom workflow controls, added general setting to disable * added util for fit to zoom that accounts for sidebar, terminal, and panel * ack PR comments * remove dead state variable, add logs * improvement(ui/ux): action bar, panel, tooltip, dragging, invite modal * added fit to view in canvas context menu * fix(theme): dark mode flash * fix: duplicate fit to view * refactor: popovers; improvement: notifications, diff controls, action bar * improvement(action-bar): ui/ux * refactor(action-bar): renamed to workflow controls * ran migrations * fix: deleted migration --------- Co-authored-by: Emir Karabeg <emirkarabeg@berkeley.edu>
1 parent e499cc4 commit 1cc489e

File tree

57 files changed

+11198
-397
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+11198
-397
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
'use client'
2+
3+
import { Tooltip } from '@/components/emcn'
4+
5+
interface TooltipProviderProps {
6+
children: React.ReactNode
7+
}
8+
9+
export function TooltipProvider({ children }: TooltipProviderProps) {
10+
return <Tooltip.Provider>{children}</Tooltip.Provider>
11+
}

apps/sim/app/_styles/globals.css

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,25 @@
5858
pointer-events: none !important;
5959
}
6060

61+
/**
62+
* Workflow canvas cursor styles
63+
* Override React Flow's default selection cursor based on canvas mode
64+
*/
65+
.workflow-container.canvas-mode-cursor .react-flow__pane,
66+
.workflow-container.canvas-mode-cursor .react-flow__selectionpane {
67+
cursor: default !important;
68+
}
69+
70+
.workflow-container.canvas-mode-hand .react-flow__pane,
71+
.workflow-container.canvas-mode-hand .react-flow__selectionpane {
72+
cursor: grab !important;
73+
}
74+
75+
.workflow-container.canvas-mode-hand .react-flow__pane:active,
76+
.workflow-container.canvas-mode-hand .react-flow__selectionpane:active {
77+
cursor: grabbing !important;
78+
}
79+
6180
/**
6281
* Selected node ring indicator
6382
* Uses a pseudo-element overlay to match the original behavior (absolute inset-0 z-40)
@@ -657,6 +676,20 @@ input[type="search"]::-ms-clear {
657676
}
658677
}
659678

679+
/**
680+
* Notification toast enter animation
681+
*/
682+
@keyframes notification-enter {
683+
from {
684+
opacity: 0;
685+
transform: translateX(-16px);
686+
}
687+
to {
688+
opacity: 1;
689+
transform: translateX(var(--stack-offset, 0px));
690+
}
691+
}
692+
660693
/**
661694
* @depricated
662695
* Legacy globals (light/dark) kept for backward-compat with old classes.

apps/sim/app/api/users/me/settings/route.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@ const SettingsSchema = z.object({
2727
superUserModeEnabled: z.boolean().optional(),
2828
errorNotificationsEnabled: z.boolean().optional(),
2929
snapToGridSize: z.number().min(0).max(50).optional(),
30+
showActionBar: z.boolean().optional(),
3031
})
3132

3233
const defaultSettings = {
33-
theme: 'system',
34+
theme: 'dark',
3435
autoConnect: true,
3536
telemetryEnabled: true,
3637
emailPreferences: {},
@@ -39,6 +40,7 @@ const defaultSettings = {
3940
superUserModeEnabled: false,
4041
errorNotificationsEnabled: true,
4142
snapToGridSize: 0,
43+
showActionBar: true,
4244
}
4345

4446
export async function GET() {
@@ -73,6 +75,7 @@ export async function GET() {
7375
superUserModeEnabled: userSettings.superUserModeEnabled ?? true,
7476
errorNotificationsEnabled: userSettings.errorNotificationsEnabled ?? true,
7577
snapToGridSize: userSettings.snapToGridSize ?? 0,
78+
showActionBar: userSettings.showActionBar ?? true,
7679
},
7780
},
7881
{ status: 200 }

apps/sim/app/layout.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { HydrationErrorHandler } from '@/app/_shell/hydration-error-handler'
1212
import { QueryProvider } from '@/app/_shell/providers/query-provider'
1313
import { SessionProvider } from '@/app/_shell/providers/session-provider'
1414
import { ThemeProvider } from '@/app/_shell/providers/theme-provider'
15+
import { TooltipProvider } from '@/app/_shell/providers/tooltip-provider'
1516
import { season } from '@/app/_styles/fonts/season/season'
1617

1718
export const viewport: Viewport = {
@@ -208,7 +209,9 @@ export default function RootLayout({ children }: { children: React.ReactNode })
208209
<ThemeProvider>
209210
<QueryProvider>
210211
<SessionProvider>
211-
<BrandedLayout>{children}</BrandedLayout>
212+
<TooltipProvider>
213+
<BrandedLayout>{children}</BrandedLayout>
214+
</TooltipProvider>
212215
</SessionProvider>
213216
</QueryProvider>
214217
</ThemeProvider>

apps/sim/app/playground/page.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,15 @@ import {
2121
Combobox,
2222
Connections,
2323
Copy,
24+
Cursor,
2425
DatePicker,
2526
DocumentAttachment,
2627
Duplicate,
28+
Expand,
2729
Eye,
2830
FolderCode,
2931
FolderPlus,
32+
Hand,
3033
HexSimple,
3134
Input,
3235
Key as KeyIcon,
@@ -991,11 +994,14 @@ export default function PlaygroundPage() {
991994
{ Icon: ChevronDown, name: 'ChevronDown' },
992995
{ Icon: Connections, name: 'Connections' },
993996
{ Icon: Copy, name: 'Copy' },
997+
{ Icon: Cursor, name: 'Cursor' },
994998
{ Icon: DocumentAttachment, name: 'DocumentAttachment' },
995999
{ Icon: Duplicate, name: 'Duplicate' },
1000+
{ Icon: Expand, name: 'Expand' },
9961001
{ Icon: Eye, name: 'Eye' },
9971002
{ Icon: FolderCode, name: 'FolderCode' },
9981003
{ Icon: FolderPlus, name: 'FolderPlus' },
1004+
{ Icon: Hand, name: 'Hand' },
9991005
{ Icon: HexSimple, name: 'HexSimple' },
10001006
{ Icon: KeyIcon, name: 'Key' },
10011007
{ Icon: Layout, name: 'Layout' },
Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
'use client'
22

3-
import { Tooltip } from '@/components/emcn'
43
import { season } from '@/app/_styles/fonts/season/season'
54

65
export default function TemplatesLayoutClient({ children }: { children: React.ReactNode }) {
76
return (
8-
<Tooltip.Provider delayDuration={600} skipDelayDuration={0}>
9-
<div className={`${season.variable} relative flex min-h-screen flex-col font-season`}>
10-
<div className='-z-50 pointer-events-none fixed inset-0 bg-white' />
11-
{children}
12-
</div>
13-
</Tooltip.Provider>
7+
<div className={`${season.variable} relative flex min-h-screen flex-col font-season`}>
8+
<div className='-z-50 pointer-events-none fixed inset-0 bg-white' />
9+
{children}
10+
</div>
1411
)
1512
}

apps/sim/app/workspace/[workspaceId]/layout.tsx

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

3-
import { Tooltip } from '@/components/emcn'
43
import { GlobalCommandsProvider } from '@/app/workspace/[workspaceId]/providers/global-commands-provider'
54
import { ProviderModelsLoader } from '@/app/workspace/[workspaceId]/providers/provider-models-loader'
65
import { SettingsLoader } from '@/app/workspace/[workspaceId]/providers/settings-loader'
@@ -13,16 +12,14 @@ export default function WorkspaceLayout({ children }: { children: React.ReactNod
1312
<SettingsLoader />
1413
<ProviderModelsLoader />
1514
<GlobalCommandsProvider>
16-
<Tooltip.Provider delayDuration={600} skipDelayDuration={0}>
17-
<div className='flex h-screen w-full bg-[var(--bg)]'>
18-
<WorkspacePermissionsProvider>
19-
<div className='shrink-0' suppressHydrationWarning>
20-
<Sidebar />
21-
</div>
22-
{children}
23-
</WorkspacePermissionsProvider>
24-
</div>
25-
</Tooltip.Provider>
15+
<div className='flex h-screen w-full bg-[var(--bg)]'>
16+
<WorkspacePermissionsProvider>
17+
<div className='shrink-0' suppressHydrationWarning>
18+
<Sidebar />
19+
</div>
20+
{children}
21+
</WorkspacePermissionsProvider>
22+
</div>
2623
</GlobalCommandsProvider>
2724
</>
2825
)

apps/sim/app/workspace/[workspaceId]/utils/commands-utils.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export type CommandId =
1919
| 'clear-terminal-console'
2020
| 'focus-toolbar-search'
2121
| 'clear-notifications'
22+
| 'fit-to-view'
2223

2324
/**
2425
* Static metadata for a global command.
@@ -104,6 +105,11 @@ export const COMMAND_DEFINITIONS: Record<CommandId, CommandDefinition> = {
104105
shortcut: 'Mod+E',
105106
allowInEditable: false,
106107
},
108+
'fit-to-view': {
109+
id: 'fit-to-view',
110+
shortcut: 'Mod+Shift+F',
111+
allowInEditable: false,
112+
},
107113
}
108114

109115
/**

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/context-menu/block-context-menu.tsx renamed to apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/block-menu/block-menu.tsx

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,55 @@
11
'use client'
22

3+
import type { RefObject } from 'react'
34
import {
45
Popover,
56
PopoverAnchor,
67
PopoverContent,
78
PopoverDivider,
89
PopoverItem,
910
} from '@/components/emcn'
10-
import type { BlockContextMenuProps } from './types'
11+
12+
/**
13+
* Block information for context menu actions
14+
*/
15+
export interface BlockInfo {
16+
id: string
17+
type: string
18+
enabled: boolean
19+
horizontalHandles: boolean
20+
parentId?: string
21+
parentType?: string
22+
}
23+
24+
/**
25+
* Props for BlockMenu component
26+
*/
27+
export interface BlockMenuProps {
28+
isOpen: boolean
29+
position: { x: number; y: number }
30+
menuRef: RefObject<HTMLDivElement | null>
31+
onClose: () => void
32+
selectedBlocks: BlockInfo[]
33+
onCopy: () => void
34+
onPaste: () => void
35+
onDuplicate: () => void
36+
onDelete: () => void
37+
onToggleEnabled: () => void
38+
onToggleHandles: () => void
39+
onRemoveFromSubflow: () => void
40+
onOpenEditor: () => void
41+
onRename: () => void
42+
hasClipboard?: boolean
43+
showRemoveFromSubflow?: boolean
44+
disableEdit?: boolean
45+
}
1146

1247
/**
1348
* Context menu for workflow block(s).
1449
* Displays block-specific actions in a popover at right-click position.
1550
* Supports multi-selection - actions apply to all selected blocks.
1651
*/
17-
export function BlockContextMenu({
52+
export function BlockMenu({
1853
isOpen,
1954
position,
2055
menuRef,
@@ -32,7 +67,7 @@ export function BlockContextMenu({
3267
hasClipboard = false,
3368
showRemoveFromSubflow = false,
3469
disableEdit = false,
35-
}: BlockContextMenuProps) {
70+
}: BlockMenuProps) {
3671
const isSingleBlock = selectedBlocks.length === 1
3772

3873
const allEnabled = selectedBlocks.every((b) => b.enabled)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export type { BlockInfo, BlockMenuProps } from './block-menu'
2+
export { BlockMenu } from './block-menu'

0 commit comments

Comments
 (0)