|
1 | | -import { useEffect, useRef, useState } from 'react' |
| 1 | +import { useCallback, useEffect, useRef, useState } from 'react' |
2 | 2 | import { Plus } from 'lucide-react' |
3 | 3 | import { useParams } from 'next/navigation' |
| 4 | +import 'prismjs/components/prism-json' |
| 5 | +import Editor from 'react-simple-code-editor' |
4 | 6 | import { |
5 | 7 | Badge, |
6 | 8 | Button, |
| 9 | + Code, |
7 | 10 | Combobox, |
8 | 11 | type ComboboxOption, |
| 12 | + calculateGutterWidth, |
| 13 | + getCodeEditorProps, |
| 14 | + highlight, |
9 | 15 | Input, |
10 | 16 | Label, |
11 | | - Textarea, |
| 17 | + languages, |
12 | 18 | } from '@/components/emcn' |
13 | 19 | import { Trash } from '@/components/emcn/icons/trash' |
14 | 20 | import { cn } from '@/lib/core/utils/cn' |
@@ -39,6 +45,8 @@ interface VariablesInputProps { |
39 | 45 | disabled?: boolean |
40 | 46 | } |
41 | 47 |
|
| 48 | +const jsonHighlight = (code: string): string => highlight(code, languages.json, 'json') |
| 49 | + |
42 | 50 | const DEFAULT_ASSIGNMENT: Omit<VariableAssignment, 'id'> = { |
43 | 51 | variableName: '', |
44 | 52 | type: 'string', |
@@ -172,6 +180,23 @@ export function VariablesInput({ |
172 | 180 | setStoreValue(assignments.map((a) => (a.id === id ? { ...a, ...updates } : a))) |
173 | 181 | } |
174 | 182 |
|
| 183 | + const updateAssignmentRef = useRef(updateAssignment) |
| 184 | + updateAssignmentRef.current = updateAssignment |
| 185 | + |
| 186 | + const editorValueChangeHandlersRef = useRef<Record<string, (newValue: string) => void>>({}) |
| 187 | + |
| 188 | + const getEditorValueChangeHandler = useCallback( |
| 189 | + (assignmentId: string): ((newValue: string) => void) => { |
| 190 | + if (!editorValueChangeHandlersRef.current[assignmentId]) { |
| 191 | + editorValueChangeHandlersRef.current[assignmentId] = (newValue: string) => { |
| 192 | + updateAssignmentRef.current(assignmentId, { value: newValue }) |
| 193 | + } |
| 194 | + } |
| 195 | + return editorValueChangeHandlersRef.current[assignmentId] |
| 196 | + }, |
| 197 | + [] |
| 198 | + ) |
| 199 | + |
175 | 200 | const handleVariableSelect = (assignmentId: string, variableId: string) => { |
176 | 201 | const selectedVariable = currentWorkflowVariables.find((v) => v.id === variableId) |
177 | 202 | if (selectedVariable) { |
@@ -402,70 +427,44 @@ export function VariablesInput({ |
402 | 427 | disabled={isReadOnly} |
403 | 428 | /> |
404 | 429 | ) : assignment.type === 'object' || assignment.type === 'array' ? ( |
405 | | - <div className='relative'> |
406 | | - <Textarea |
407 | | - ref={(el) => { |
408 | | - if (el) valueInputRefs.current[assignment.id] = el |
409 | | - }} |
410 | | - value={assignment.value || ''} |
411 | | - onChange={(e) => |
412 | | - handleValueInputChange( |
413 | | - assignment.id, |
414 | | - e.target.value, |
415 | | - e.target.selectionStart ?? undefined |
416 | | - ) |
417 | | - } |
418 | | - onKeyDown={handleKeyDown} |
419 | | - onFocus={() => { |
420 | | - if (!isReadOnly && !assignment.value?.trim()) { |
421 | | - setActiveFieldId(assignment.id) |
422 | | - setCursorPosition(0) |
423 | | - setShowTags(true) |
424 | | - } |
425 | | - }} |
426 | | - onScroll={(e) => { |
427 | | - const overlay = overlayRefs.current[assignment.id] |
428 | | - if (overlay) { |
429 | | - overlay.scrollTop = e.currentTarget.scrollTop |
430 | | - overlay.scrollLeft = e.currentTarget.scrollLeft |
431 | | - } |
432 | | - }} |
433 | | - placeholder={ |
434 | | - assignment.type === 'object' |
435 | | - ? '{\n "key": "value"\n}' |
436 | | - : '[\n 1, 2, 3\n]' |
437 | | - } |
438 | | - disabled={isReadOnly} |
439 | | - className={cn( |
440 | | - 'min-h-[120px] font-mono text-sm text-transparent caret-foreground placeholder:text-muted-foreground/50', |
441 | | - dragHighlight[assignment.id] && 'ring-2 ring-blue-500 ring-offset-2' |
442 | | - )} |
443 | | - style={{ |
444 | | - wordBreak: 'break-word', |
445 | | - whiteSpace: 'pre-wrap', |
446 | | - }} |
447 | | - onDrop={(e) => handleDrop(e, assignment.id)} |
448 | | - onDragOver={(e) => handleDragOver(e, assignment.id)} |
449 | | - onDragLeave={(e) => handleDragLeave(e, assignment.id)} |
450 | | - /> |
451 | | - <div |
452 | | - ref={(el) => { |
453 | | - if (el) overlayRefs.current[assignment.id] = el |
454 | | - }} |
455 | | - className={cn( |
456 | | - 'absolute inset-0 flex items-start overflow-auto bg-transparent px-3 py-2 font-mono text-sm', |
457 | | - !isReadOnly && 'pointer-events-none' |
458 | | - )} |
459 | | - style={{ scrollbarWidth: 'none' }} |
460 | | - > |
461 | | - <div className='w-full whitespace-pre-wrap break-words'> |
462 | | - {formatDisplayText(assignment.value || '', { |
463 | | - accessiblePrefixes, |
464 | | - highlightAll: !accessiblePrefixes, |
465 | | - })} |
466 | | - </div> |
467 | | - </div> |
468 | | - </div> |
| 430 | + (() => { |
| 431 | + const fieldValue = assignment.value || '' |
| 432 | + const lineCount = fieldValue.split('\n').length |
| 433 | + const gutterWidth = calculateGutterWidth(lineCount) |
| 434 | + |
| 435 | + return ( |
| 436 | + <Code.Container className='min-h-[120px]'> |
| 437 | + <Code.Gutter width={gutterWidth}> |
| 438 | + {Array.from({ length: lineCount }, (_, i) => ( |
| 439 | + <div |
| 440 | + key={i} |
| 441 | + className='font-medium font-mono text-[var(--text-muted)] text-xs' |
| 442 | + style={{ height: `${21}px`, lineHeight: `${21}px` }} |
| 443 | + > |
| 444 | + {i + 1} |
| 445 | + </div> |
| 446 | + ))} |
| 447 | + </Code.Gutter> |
| 448 | + <Code.Content paddingLeft={`${gutterWidth}px`}> |
| 449 | + <Code.Placeholder |
| 450 | + gutterWidth={gutterWidth} |
| 451 | + show={fieldValue.length === 0} |
| 452 | + > |
| 453 | + {assignment.type === 'object' |
| 454 | + ? '{\n "key": "value"\n}' |
| 455 | + : '[1, 2, 3]'} |
| 456 | + </Code.Placeholder> |
| 457 | + <Editor |
| 458 | + value={fieldValue} |
| 459 | + onValueChange={getEditorValueChangeHandler(assignment.id)} |
| 460 | + highlight={jsonHighlight} |
| 461 | + disabled={isReadOnly} |
| 462 | + {...getCodeEditorProps({ disabled: isReadOnly })} |
| 463 | + /> |
| 464 | + </Code.Content> |
| 465 | + </Code.Container> |
| 466 | + ) |
| 467 | + })() |
469 | 468 | ) : ( |
470 | 469 | <div className='relative'> |
471 | 470 | <Input |
|
0 commit comments