11'use client'
22
3+ import { useCallback } from 'react'
34import { Layout , LibraryBig , Search } from 'lucide-react'
45import Image from 'next/image'
6+ import { useParams , useRouter } from 'next/navigation'
57import { Button } from '@/components/emcn'
68import { AgentIcon } from '@/components/icons'
9+ import { createLogger } from '@/lib/logs/console/logger'
710import { cn } from '@/lib/utils'
11+ import { useSearchModalStore } from '@/stores/search-modal/store'
12+
13+ const logger = createLogger ( 'WorkflowCommandList' )
814
915/**
1016 * Command item data structure
@@ -49,13 +55,131 @@ const commands: CommandItem[] = [
4955 * Centered on the screen for empty workflows
5056 */
5157export function CommandList ( ) {
58+ const params = useParams ( )
59+ const router = useRouter ( )
60+ const { open : openSearchModal } = useSearchModalStore ( )
61+
62+ const workspaceId = params . workspaceId as string | undefined
63+
64+ /**
65+ * Handle click on a command row.
66+ *
67+ * Mirrors the behavior of the corresponding global keyboard shortcuts:
68+ * - Templates: navigate to workspace templates
69+ * - New Agent: add an agent block to the canvas
70+ * - Logs: navigate to workspace logs
71+ * - Search Blocks: open the universal search modal
72+ *
73+ * @param label - Command label that was clicked.
74+ */
75+ const handleCommandClick = useCallback (
76+ ( label : string ) => {
77+ try {
78+ switch ( label ) {
79+ case 'Templates' : {
80+ if ( ! workspaceId ) {
81+ logger . warn ( 'No workspace ID found, cannot navigate to templates from command list' )
82+ return
83+ }
84+ router . push ( `/workspace/${ workspaceId } /templates` )
85+ return
86+ }
87+ case 'New Agent' : {
88+ const event = new CustomEvent ( 'add-block-from-toolbar' , {
89+ detail : { type : 'agent' , enableTriggerMode : false } ,
90+ } )
91+ window . dispatchEvent ( event )
92+ return
93+ }
94+ case 'Logs' : {
95+ if ( ! workspaceId ) {
96+ logger . warn ( 'No workspace ID found, cannot navigate to logs from command list' )
97+ return
98+ }
99+ router . push ( `/workspace/${ workspaceId } /logs` )
100+ return
101+ }
102+ case 'Search Blocks' : {
103+ openSearchModal ( )
104+ return
105+ }
106+ default :
107+ logger . warn ( 'Unknown command label clicked in command list' , { label } )
108+ }
109+ } catch ( error ) {
110+ logger . error ( 'Failed to handle command click in command list' , { error, label } )
111+ }
112+ } ,
113+ [ router , workspaceId , openSearchModal ]
114+ )
115+
116+ /**
117+ * Handle drag-over events from the toolbar.
118+ *
119+ * When a toolbar item is dragged over the command list, mark the drop as valid
120+ * so the browser shows the appropriate drop cursor. Only reacts to toolbar
121+ * drags that carry the expected JSON payload.
122+ *
123+ * @param event - Drag event from the browser.
124+ */
125+ const handleDragOver = useCallback ( ( event : React . DragEvent < HTMLDivElement > ) => {
126+ if ( ! event . dataTransfer ?. types . includes ( 'application/json' ) ) {
127+ return
128+ }
129+ event . preventDefault ( )
130+ event . dataTransfer . dropEffect = 'move'
131+ } , [ ] )
132+
133+ /**
134+ * Handle drops of toolbar items onto the command list.
135+ *
136+ * This forwards the drop information (block type and cursor position)
137+ * to the workflow canvas via a custom event. The workflow component
138+ * then reuses its existing drop logic to place the block precisely
139+ * under the cursor, including container/subflow handling.
140+ *
141+ * @param event - Drop event from the browser.
142+ */
143+ const handleDrop = useCallback ( ( event : React . DragEvent < HTMLDivElement > ) => {
144+ if ( ! event . dataTransfer ?. types . includes ( 'application/json' ) ) {
145+ return
146+ }
147+
148+ event . preventDefault ( )
149+
150+ try {
151+ const raw = event . dataTransfer . getData ( 'application/json' )
152+ if ( ! raw ) return
153+
154+ const data = JSON . parse ( raw ) as { type ?: string ; enableTriggerMode ?: boolean }
155+ if ( ! data ?. type || data . type === 'connectionBlock' ) return
156+
157+ const overlayDropEvent = new CustomEvent ( 'toolbar-drop-on-empty-workflow-overlay' , {
158+ detail : {
159+ type : data . type ,
160+ enableTriggerMode : data . enableTriggerMode ?? false ,
161+ clientX : event . clientX ,
162+ clientY : event . clientY ,
163+ } ,
164+ } )
165+
166+ window . dispatchEvent ( overlayDropEvent )
167+ } catch ( error ) {
168+ logger . error ( 'Failed to handle drop on command list' , { error } )
169+ }
170+ } , [ ] )
171+
52172 return (
53173 < div
54174 className = { cn (
55175 'pointer-events-none absolute inset-0 mb-[50px] flex items-center justify-center'
56176 ) }
57177 >
58- < div className = 'pointer-events-none flex flex-col gap-[8px]' >
178+ < div
179+ className = 'pointer-events-auto flex flex-col gap-[8px]'
180+ onDragOver = { handleDragOver }
181+ onDrop = { handleDrop }
182+ >
59183 { /* Logo */ }
60184 < div className = 'mb-[20px] flex justify-center' >
61185 < Image
@@ -79,6 +203,7 @@ export function CommandList() {
79203 < div
80204 key = { command . label }
81205 className = 'group flex cursor-pointer items-center justify-between gap-[60px]'
206+ onClick = { ( ) => handleCommandClick ( command . label ) }
82207 >
83208 { /* Left side: Icon and Label */ }
84209 < div className = 'flex items-center gap-[8px]' >
@@ -91,15 +216,15 @@ export function CommandList() {
91216 { /* Right side: Keyboard Shortcut */ }
92217 < div className = 'flex items-center gap-[4px]' >
93218 < Button
94- className = 'group-hover:-translate-y-0.5 w-[26px] py-[3px] text-[12px] hover:translate-y-0 hover:text-[var(--text-tertiary)] hover:shadow-[0_2px_0_0 ] group-hover:text-[var(--text-primary)] group-hover:shadow-[0_4px_0_0 ]'
219+ className = 'group-hover:-translate-y-0.5 w-[26px] py-[3px] text-[12px] hover:translate-y-0 hover:text-[var(--text-tertiary)] hover:shadow-[0_2px_0_0_rgba(48,48,48,1) ] group-hover:text-[var(--text-primary)] group-hover:shadow-[0_4px_0_0_rgba(48,48,48,1) ]'
95220 variant = '3d'
96221 >
97222 < span > ⌘</ span >
98223 </ Button >
99224 { shortcuts . map ( ( key , index ) => (
100225 < Button
101226 key = { index }
102- className = 'group-hover:-translate-y-0.5 w-[26px] py-[3px] text-[12px] hover:translate-y-0 hover:text-[var(--text-tertiary)] hover:shadow-[0_2px_0_0 ] group-hover:text-[var(--text-primary)] group-hover:shadow-[0_4px_0_0 ]'
227+ className = 'group-hover:-translate-y-0.5 w-[26px] py-[3px] text-[12px] hover:translate-y-0 hover:text-[var(--text-tertiary)] hover:shadow-[0_2px_0_0_rgba(48,48,48,1) ] group-hover:text-[var(--text-primary)] group-hover:shadow-[0_4px_0_0_rgba(48,48,48,1) ]'
103228 variant = '3d'
104229 >
105230 { key }
0 commit comments