mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 15:07:55 -05:00
Reverted code block and removed conditional-input; trying solution with codeblock params
This commit is contained in:
@@ -11,23 +11,9 @@ interface CodeProps {
|
|||||||
blockId: string
|
blockId: string
|
||||||
subBlockId: string
|
subBlockId: string
|
||||||
isConnecting: boolean
|
isConnecting: boolean
|
||||||
inConditionSubBlock?: boolean
|
|
||||||
value?: string
|
|
||||||
onChange?: (value: string) => void
|
|
||||||
controlled?: boolean
|
|
||||||
onSourceBlockIdChange?: (blockId: string | null) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Code({
|
export function Code({ blockId, subBlockId, isConnecting }: CodeProps) {
|
||||||
blockId,
|
|
||||||
subBlockId,
|
|
||||||
isConnecting,
|
|
||||||
inConditionSubBlock,
|
|
||||||
value: controlledValue,
|
|
||||||
onChange,
|
|
||||||
controlled = false,
|
|
||||||
onSourceBlockIdChange,
|
|
||||||
}: CodeProps) {
|
|
||||||
const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlockId)
|
const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlockId)
|
||||||
const [code, setCode] = useState('')
|
const [code, setCode] = useState('')
|
||||||
const [lineCount, setLineCount] = useState(1)
|
const [lineCount, setLineCount] = useState(1)
|
||||||
@@ -39,14 +25,12 @@ export function Code({
|
|||||||
// Add new state for tracking visual line heights
|
// Add new state for tracking visual line heights
|
||||||
const [visualLineHeights, setVisualLineHeights] = useState<number[]>([])
|
const [visualLineHeights, setVisualLineHeights] = useState<number[]>([])
|
||||||
|
|
||||||
// Modify the useEffect to handle both controlled and uncontrolled modes
|
// Sync code with store value on initial load and when store value changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (controlled) {
|
if (storeValue !== null) {
|
||||||
setCode(controlledValue || '')
|
|
||||||
} else if (storeValue !== null) {
|
|
||||||
setCode(storeValue.toString())
|
setCode(storeValue.toString())
|
||||||
}
|
}
|
||||||
}, [storeValue, controlledValue, controlled])
|
}, [storeValue])
|
||||||
|
|
||||||
// Update the line counting logic to account for wrapped lines
|
// Update the line counting logic to account for wrapped lines
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -145,91 +129,30 @@ export function Code({
|
|||||||
return numbers
|
return numbers
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCodeChange = (newCode: string) => {
|
// Handle drops from connection blocks
|
||||||
setCode(newCode)
|
|
||||||
if (controlled) {
|
|
||||||
onChange?.(newCode)
|
|
||||||
} else {
|
|
||||||
setStoreValue(newCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the textarea element
|
|
||||||
const textarea = editorRef.current?.querySelector('textarea')
|
|
||||||
if (textarea) {
|
|
||||||
// Important: Use requestAnimationFrame to ensure we get the updated cursor position
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
const pos = textarea.selectionStart
|
|
||||||
setCursorPosition(pos)
|
|
||||||
|
|
||||||
const trigger = checkTagTrigger(newCode, pos)
|
|
||||||
setShowTags(trigger.show)
|
|
||||||
if (!trigger.show) {
|
|
||||||
setActiveSourceBlockId(null)
|
|
||||||
onSourceBlockIdChange?.(null)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add an onKeyDown handler to ensure we catch the '<' character immediately
|
|
||||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
||||||
if (e.key === '<') {
|
|
||||||
const textarea = e.target as HTMLTextAreaElement
|
|
||||||
const pos = textarea.selectionStart
|
|
||||||
const newCode = code.slice(0, pos) + '<' + code.slice(pos)
|
|
||||||
|
|
||||||
setCode(newCode)
|
|
||||||
if (controlled) {
|
|
||||||
onChange?.(newCode)
|
|
||||||
} else {
|
|
||||||
setStoreValue(newCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
setCursorPosition(pos + 1)
|
|
||||||
setShowTags(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle tag selection
|
|
||||||
const handleTagSelect = (newValue: string) => {
|
|
||||||
setCode(newValue)
|
|
||||||
if (controlled) {
|
|
||||||
onChange?.(newValue)
|
|
||||||
} else {
|
|
||||||
setStoreValue(newValue)
|
|
||||||
}
|
|
||||||
setShowTags(false)
|
|
||||||
setActiveSourceBlockId(null)
|
|
||||||
onSourceBlockIdChange?.(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Modify handleDrop to support both controlled and uncontrolled modes
|
|
||||||
const handleDrop = (e: React.DragEvent) => {
|
const handleDrop = (e: React.DragEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(e.dataTransfer.getData('application/json'))
|
const data = JSON.parse(e.dataTransfer.getData('application/json'))
|
||||||
if (data.type !== 'connectionBlock') return
|
if (data.type !== 'connectionBlock') return
|
||||||
|
|
||||||
|
// Get current cursor position from the textarea
|
||||||
const textarea = editorRef.current?.querySelector('textarea')
|
const textarea = editorRef.current?.querySelector('textarea')
|
||||||
const dropPosition = textarea?.selectionStart ?? code.length
|
const dropPosition = textarea?.selectionStart ?? code.length
|
||||||
|
|
||||||
|
// Insert '<' at drop position to trigger the dropdown
|
||||||
const newValue = code.slice(0, dropPosition) + '<' + code.slice(dropPosition)
|
const newValue = code.slice(0, dropPosition) + '<' + code.slice(dropPosition)
|
||||||
|
|
||||||
setCode(newValue)
|
setCode(newValue)
|
||||||
if (controlled) {
|
setStoreValue(newValue)
|
||||||
onChange?.(newValue)
|
|
||||||
} else {
|
|
||||||
setStoreValue(newValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
setCursorPosition(dropPosition + 1)
|
setCursorPosition(dropPosition + 1)
|
||||||
setShowTags(true)
|
setShowTags(true)
|
||||||
|
|
||||||
if (data.connectionData?.sourceBlockId) {
|
if (data.connectionData?.sourceBlockId) {
|
||||||
setActiveSourceBlockId(data.connectionData.sourceBlockId)
|
setActiveSourceBlockId(data.connectionData.sourceBlockId)
|
||||||
onSourceBlockIdChange?.(data.connectionData.sourceBlockId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set cursor position after state updates
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (textarea) {
|
if (textarea) {
|
||||||
textarea.selectionStart = dropPosition + 1
|
textarea.selectionStart = dropPosition + 1
|
||||||
@@ -242,16 +165,23 @@ export function Code({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle tag selection
|
||||||
|
const handleTagSelect = (newValue: string) => {
|
||||||
|
setCode(newValue)
|
||||||
|
setStoreValue(newValue)
|
||||||
|
setShowTags(false)
|
||||||
|
setActiveSourceBlockId(null)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
!inConditionSubBlock && 'border',
|
'font-mono text-sm border rounded-md overflow-visible relative',
|
||||||
'font-mono text-sm rounded-md overflow-visible relative',
|
|
||||||
'bg-background text-muted-foreground',
|
'bg-background text-muted-foreground',
|
||||||
isConnecting && !inConditionSubBlock && 'ring-2 ring-blue-500 ring-offset-2'
|
isConnecting && 'ring-2 ring-blue-500 ring-offset-2'
|
||||||
)}
|
)}
|
||||||
onDragOver={(e) => !inConditionSubBlock && e.preventDefault()}
|
onDragOver={(e) => e.preventDefault()}
|
||||||
onDrop={(e) => !inConditionSubBlock && handleDrop(e)}
|
onDrop={handleDrop}
|
||||||
>
|
>
|
||||||
{/* Updated line numbers */}
|
{/* Updated line numbers */}
|
||||||
<div
|
<div
|
||||||
@@ -264,13 +194,27 @@ export function Code({
|
|||||||
<div ref={editorRef} className="pl-[30px] pt-0 mt-0 relative">
|
<div ref={editorRef} className="pl-[30px] pt-0 mt-0 relative">
|
||||||
{code.length === 0 && (
|
{code.length === 0 && (
|
||||||
<div className="absolute left-[42px] top-[12px] text-muted-foreground/50 select-none pointer-events-none">
|
<div className="absolute left-[42px] top-[12px] text-muted-foreground/50 select-none pointer-events-none">
|
||||||
{inConditionSubBlock ? '<response> === true' : 'Write JavaScript...'}
|
Write JavaScript...
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Editor
|
<Editor
|
||||||
value={code}
|
value={code}
|
||||||
onValueChange={handleCodeChange}
|
onValueChange={(newCode) => {
|
||||||
onKeyDown={handleKeyDown}
|
setCode(newCode)
|
||||||
|
setStoreValue(newCode)
|
||||||
|
|
||||||
|
// Check for tag trigger
|
||||||
|
const textarea = editorRef.current?.querySelector('textarea')
|
||||||
|
if (textarea) {
|
||||||
|
const pos = textarea.selectionStart
|
||||||
|
setCursorPosition(pos)
|
||||||
|
const trigger = checkTagTrigger(newCode, pos)
|
||||||
|
setShowTags(trigger.show)
|
||||||
|
if (!trigger.show) {
|
||||||
|
setActiveSourceBlockId(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
highlight={(code) => highlight(code, languages.javascript, 'javascript')}
|
highlight={(code) => highlight(code, languages.javascript, 'javascript')}
|
||||||
padding={12}
|
padding={12}
|
||||||
style={{
|
style={{
|
||||||
@@ -283,21 +227,18 @@ export function Code({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{showTags && (
|
{showTags && (
|
||||||
<div className="absolute left-0 right-0 top-full z-50">
|
<TagDropdown
|
||||||
<TagDropdown
|
visible={showTags}
|
||||||
visible={showTags}
|
onSelect={handleTagSelect}
|
||||||
onSelect={handleTagSelect}
|
blockId={blockId}
|
||||||
blockId={blockId}
|
activeSourceBlockId={activeSourceBlockId}
|
||||||
activeSourceBlockId={activeSourceBlockId}
|
inputValue={code}
|
||||||
inputValue={code}
|
cursorPosition={cursorPosition}
|
||||||
cursorPosition={cursorPosition}
|
onClose={() => {
|
||||||
onClose={() => {
|
setShowTags(false)
|
||||||
setShowTags(false)
|
setActiveSourceBlockId(null)
|
||||||
setActiveSourceBlockId(null)
|
}}
|
||||||
onSourceBlockIdChange?.(null)
|
/>
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,151 +0,0 @@
|
|||||||
import { useState } from 'react'
|
|
||||||
import { PlusIcon, XIcon } from 'lucide-react'
|
|
||||||
import { Button } from '@/components/ui/button'
|
|
||||||
import { TagDropdown, checkTagTrigger } from '@/components/ui/tag-dropdown'
|
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
import { useSubBlockValue } from '../hooks/use-sub-block-value'
|
|
||||||
import { Code } from './code'
|
|
||||||
|
|
||||||
interface ConditionInputProps {
|
|
||||||
blockId: string
|
|
||||||
subBlockId: string
|
|
||||||
isConnecting: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Condition {
|
|
||||||
id: string
|
|
||||||
type: 'if' | 'else if' | 'else'
|
|
||||||
code: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ConditionInput({ blockId, subBlockId, isConnecting }: ConditionInputProps) {
|
|
||||||
const [value, setValue] = useSubBlockValue(blockId, subBlockId)
|
|
||||||
const [showTags, setShowTags] = useState(false)
|
|
||||||
const [cursorPosition, setCursorPosition] = useState(0)
|
|
||||||
const [activeSourceBlockId, setActiveSourceBlockId] = useState<string | null>(null)
|
|
||||||
const [activeConditionId, setActiveConditionId] = useState<string | null>(null)
|
|
||||||
|
|
||||||
// Initialize with default if/else conditions if no value exists
|
|
||||||
const conditions: Condition[] =
|
|
||||||
Array.isArray(value) && value.length > 0 && 'type' in value[0]
|
|
||||||
? (value as unknown as Condition[])
|
|
||||||
: [
|
|
||||||
{ id: crypto.randomUUID(), type: 'if', code: '' },
|
|
||||||
{ id: crypto.randomUUID(), type: 'else', code: '' },
|
|
||||||
]
|
|
||||||
|
|
||||||
const addCondition = (afterId: string) => {
|
|
||||||
const index = conditions.findIndex((c) => c.id === afterId)
|
|
||||||
const newCondition: Condition = {
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
type: 'else if',
|
|
||||||
code: '',
|
|
||||||
}
|
|
||||||
const newConditions = [
|
|
||||||
...conditions.slice(0, index + 1),
|
|
||||||
newCondition,
|
|
||||||
...conditions.slice(index + 1),
|
|
||||||
]
|
|
||||||
setValue(newConditions)
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeCondition = (id: string) => {
|
|
||||||
setValue(conditions.filter((c) => c.id !== id))
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateCode = (id: string, code: string) => {
|
|
||||||
setValue(conditions.map((c) => (c.id === id ? { ...c, code } : c)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle tag selection
|
|
||||||
const handleTagSelect = (newValue: string) => {
|
|
||||||
if (activeConditionId) {
|
|
||||||
const condition = conditions.find((c) => c.id === activeConditionId)
|
|
||||||
if (condition) {
|
|
||||||
updateCode(activeConditionId, newValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setShowTags(false)
|
|
||||||
setActiveSourceBlockId(null)
|
|
||||||
setActiveConditionId(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle code changes and tag triggers
|
|
||||||
const handleCodeChange = (conditionId: string, newCode: string) => {
|
|
||||||
updateCode(conditionId, newCode)
|
|
||||||
|
|
||||||
// Check for tag trigger
|
|
||||||
const trigger = checkTagTrigger(newCode, cursorPosition)
|
|
||||||
if (trigger.show) {
|
|
||||||
setShowTags(true)
|
|
||||||
setActiveConditionId(conditionId)
|
|
||||||
} else {
|
|
||||||
setShowTags(false)
|
|
||||||
setActiveSourceBlockId(null)
|
|
||||||
setActiveConditionId(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="space-y-4">
|
|
||||||
{conditions.map((condition) => (
|
|
||||||
<div key={condition.id} className="group flex flex-col w-full relative">
|
|
||||||
<div className="rounded-md border overflow-hidden">
|
|
||||||
<div className="flex items-center justify-between px-3 py-1.5 border-b bg-background">
|
|
||||||
<span className="text-sm font-medium text-muted-foreground">{condition.type}</span>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{condition.type !== 'else' && (
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => addCondition(condition.id)}
|
|
||||||
className="h-6 px-2 text-xs text-muted-foreground hover:text-foreground"
|
|
||||||
>
|
|
||||||
<PlusIcon className="w-3 h-3 mr-1" />
|
|
||||||
Add
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{condition.type !== 'if' && (
|
|
||||||
<button
|
|
||||||
onClick={() => removeCondition(condition.id)}
|
|
||||||
className="text-muted-foreground hover:text-foreground"
|
|
||||||
>
|
|
||||||
<XIcon className="w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Code
|
|
||||||
blockId={blockId}
|
|
||||||
subBlockId={condition.id}
|
|
||||||
isConnecting={isConnecting}
|
|
||||||
inConditionSubBlock={true}
|
|
||||||
value={condition.code}
|
|
||||||
onChange={(newCode) => handleCodeChange(condition.id, newCode)}
|
|
||||||
controlled={true}
|
|
||||||
onSourceBlockIdChange={setActiveSourceBlockId}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{showTags && activeConditionId === condition.id && (
|
|
||||||
<div className="absolute left-0 right-0 top-full z-50">
|
|
||||||
<TagDropdown
|
|
||||||
visible={showTags}
|
|
||||||
onSelect={handleTagSelect}
|
|
||||||
blockId={blockId}
|
|
||||||
activeSourceBlockId={activeSourceBlockId}
|
|
||||||
inputValue={condition.code}
|
|
||||||
cursorPosition={cursorPosition}
|
|
||||||
onClose={() => {
|
|
||||||
setShowTags(false)
|
|
||||||
setActiveSourceBlockId(null)
|
|
||||||
setActiveConditionId(null)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,6 @@ import { Label } from '@/components/ui/label'
|
|||||||
import { SubBlockConfig } from '../../../../../../../blocks/types'
|
import { SubBlockConfig } from '../../../../../../../blocks/types'
|
||||||
import { CheckboxList } from './components/checkbox-list'
|
import { CheckboxList } from './components/checkbox-list'
|
||||||
import { Code } from './components/code'
|
import { Code } from './components/code'
|
||||||
import { ConditionInput } from './components/condition-input'
|
|
||||||
import { Dropdown } from './components/dropdown'
|
import { Dropdown } from './components/dropdown'
|
||||||
import { LongInput } from './components/long-input'
|
import { LongInput } from './components/long-input'
|
||||||
import { ShortInput } from './components/short-input'
|
import { ShortInput } from './components/short-input'
|
||||||
@@ -83,10 +82,6 @@ export function SubBlock({ blockId, config, isConnecting }: SubBlockProps) {
|
|||||||
layout={config.layout}
|
layout={config.layout}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
case 'condition-input':
|
|
||||||
return (
|
|
||||||
<ConditionInput blockId={blockId} subBlockId={config.id} isConnecting={isConnecting} />
|
|
||||||
)
|
|
||||||
default:
|
default:
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ export const ConditionBlock: BlockConfig<CodeExecutionOutput> = {
|
|||||||
},
|
},
|
||||||
subBlocks: [
|
subBlocks: [
|
||||||
{
|
{
|
||||||
id: 'conditions',
|
id: 'if',
|
||||||
type: 'condition-input',
|
type: 'code',
|
||||||
layout: 'full',
|
layout: 'full',
|
||||||
outputHandle: true,
|
outputHandle: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ export type SubBlockType =
|
|||||||
| 'switch'
|
| 'switch'
|
||||||
| 'tool-input'
|
| 'tool-input'
|
||||||
| 'checkbox-list'
|
| 'checkbox-list'
|
||||||
| 'condition-input'
|
|
||||||
export type SubBlockLayout = 'full' | 'half'
|
export type SubBlockLayout = 'full' | 'half'
|
||||||
|
|
||||||
export interface ParamConfig {
|
export interface ParamConfig {
|
||||||
|
|||||||
Reference in New Issue
Block a user