Skip to content

Commit 2372651

Browse files
committed
fix(variables): add drag-and-drop and tag dropdown support to code editor
1 parent 516d85b commit 2372651

File tree

1 file changed

+106
-7
lines changed
  • apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/variables-input

1 file changed

+106
-7
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/variables-input/variables-input.tsx

Lines changed: 106 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ export function VariablesInput({
102102
const [activeSourceBlockId, setActiveSourceBlockId] = useState<string | null>(null)
103103
const valueInputRefs = useRef<Record<string, HTMLInputElement | HTMLTextAreaElement>>({})
104104
const overlayRefs = useRef<Record<string, HTMLDivElement>>({})
105+
const editorContainerRefs = useRef<Record<string, HTMLDivElement | null>>({})
105106
const [dragHighlight, setDragHighlight] = useState<Record<string, boolean>>({})
106107
const [collapsedAssignments, setCollapsedAssignments] = useState<Record<string, boolean>>({})
107108

@@ -190,6 +191,26 @@ export function VariablesInput({
190191
if (!editorValueChangeHandlersRef.current[assignmentId]) {
191192
editorValueChangeHandlersRef.current[assignmentId] = (newValue: string) => {
192193
updateAssignmentRef.current(assignmentId, { value: newValue })
194+
195+
const container = editorContainerRefs.current[assignmentId]
196+
const textarea = container?.querySelector('textarea')
197+
if (textarea) {
198+
const pos = textarea.selectionStart
199+
setCursorPosition(pos)
200+
setActiveFieldId(assignmentId)
201+
202+
const tagTrigger = checkTagTrigger(newValue, pos)
203+
setShowTags(tagTrigger.show)
204+
if (tagTrigger.show) {
205+
const textBeforeCursor = newValue.slice(0, pos)
206+
const lastOpenBracket = textBeforeCursor.lastIndexOf('<')
207+
const tagContent = textBeforeCursor.slice(lastOpenBracket + 1)
208+
const dotIndex = tagContent.indexOf('.')
209+
setActiveSourceBlockId(dotIndex > 0 ? tagContent.slice(0, dotIndex) : null)
210+
} else {
211+
setActiveSourceBlockId(null)
212+
}
213+
}
193214
}
194215
}
195216
return editorValueChangeHandlersRef.current[assignmentId]
@@ -215,16 +236,27 @@ export function VariablesInput({
215236
const assignment = assignments.find((a) => a.id === activeFieldId)
216237
const originalValue = assignment?.value || ''
217238
const textAfterCursor = originalValue.slice(cursorPosition)
239+
const isCodeEditor = assignment?.type === 'object' || assignment?.type === 'array'
218240

219241
updateAssignment(activeFieldId, { value: newValue })
220242
setShowTags(false)
221243

222244
setTimeout(() => {
223-
const inputEl = valueInputRefs.current[activeFieldId]
224-
if (inputEl) {
225-
inputEl.focus()
226-
const newCursorPos = newValue.length - textAfterCursor.length
227-
inputEl.setSelectionRange(newCursorPos, newCursorPos)
245+
const newCursorPos = newValue.length - textAfterCursor.length
246+
if (isCodeEditor) {
247+
const container = editorContainerRefs.current[activeFieldId]
248+
const textarea = container?.querySelector('textarea')
249+
if (textarea) {
250+
textarea.focus()
251+
textarea.selectionStart = newCursorPos
252+
textarea.selectionEnd = newCursorPos
253+
}
254+
} else {
255+
const inputEl = valueInputRefs.current[activeFieldId]
256+
if (inputEl) {
257+
inputEl.focus()
258+
inputEl.setSelectionRange(newCursorPos, newCursorPos)
259+
}
228260
}
229261
}, 10)
230262
}
@@ -298,6 +330,39 @@ export function VariablesInput({
298330
setDragHighlight((prev) => ({ ...prev, [assignmentId]: false }))
299331
}
300332

333+
const handleEditorDrop = (e: React.DragEvent, assignmentId: string) => {
334+
if (isReadOnly) return
335+
e.preventDefault()
336+
try {
337+
const data = JSON.parse(e.dataTransfer.getData('application/json'))
338+
if (data.type !== 'connectionBlock') return
339+
340+
const container = editorContainerRefs.current[assignmentId]
341+
const textarea = container?.querySelector('textarea')
342+
const assignment = assignments.find((a) => a.id === assignmentId)
343+
const currentValue = assignment?.value || ''
344+
const dropPosition = textarea?.selectionStart ?? currentValue.length
345+
const newValue = `${currentValue.slice(0, dropPosition)}<${currentValue.slice(dropPosition)}`
346+
347+
updateAssignment(assignmentId, { value: newValue })
348+
setActiveFieldId(assignmentId)
349+
setCursorPosition(dropPosition + 1)
350+
351+
if (data.connectionData?.sourceBlockId) {
352+
setActiveSourceBlockId(data.connectionData.sourceBlockId)
353+
}
354+
355+
setTimeout(() => {
356+
if (textarea) {
357+
textarea.focus()
358+
textarea.selectionStart = dropPosition + 1
359+
textarea.selectionEnd = dropPosition + 1
360+
setShowTags(true)
361+
}
362+
}, 0)
363+
} catch {}
364+
}
365+
301366
const toggleCollapse = (assignmentId: string) => {
302367
setCollapsedAssignments((prev) => ({
303368
...prev,
@@ -433,7 +498,11 @@ export function VariablesInput({
433498
const gutterWidth = calculateGutterWidth(lineCount)
434499

435500
return (
436-
<Code.Container className='min-h-[120px]'>
501+
<Code.Container
502+
className='min-h-[120px]'
503+
onDragOver={(e) => e.preventDefault()}
504+
onDrop={(e) => handleEditorDrop(e, assignment.id)}
505+
>
437506
<Code.Gutter width={gutterWidth}>
438507
{Array.from({ length: lineCount }, (_, i) => (
439508
<div
@@ -445,7 +514,14 @@ export function VariablesInput({
445514
</div>
446515
))}
447516
</Code.Gutter>
448-
<Code.Content paddingLeft={`${gutterWidth}px`}>
517+
<Code.Content
518+
paddingLeft={`${gutterWidth}px`}
519+
editorRef={
520+
((el: HTMLDivElement | null) => {
521+
editorContainerRefs.current[assignment.id] = el
522+
}) as unknown as React.RefObject<HTMLDivElement | null>
523+
}
524+
>
449525
<Code.Placeholder
450526
gutterWidth={gutterWidth}
451527
show={fieldValue.length === 0}
@@ -461,6 +537,29 @@ export function VariablesInput({
461537
disabled={isReadOnly}
462538
{...getCodeEditorProps({ disabled: isReadOnly })}
463539
/>
540+
{showTags && activeFieldId === assignment.id && (
541+
<TagDropdown
542+
visible={showTags}
543+
onSelect={handleTagSelect}
544+
blockId={blockId}
545+
activeSourceBlockId={activeSourceBlockId}
546+
inputValue={fieldValue}
547+
cursorPosition={cursorPosition}
548+
onClose={() => {
549+
setShowTags(false)
550+
setActiveSourceBlockId(null)
551+
}}
552+
inputRef={
553+
{
554+
current:
555+
(editorContainerRefs.current[
556+
assignment.id
557+
]?.querySelector('textarea') as HTMLTextAreaElement) ??
558+
null,
559+
} as React.RefObject<HTMLTextAreaElement | HTMLInputElement>
560+
}
561+
/>
562+
)}
464563
</Code.Content>
465564
</Code.Container>
466565
)

0 commit comments

Comments
 (0)