improvement(API): added query params, fixed cursor ref, code placeholder

This commit is contained in:
Emir Karabeg
2025-02-18 13:20:49 -08:00
parent 8787f2dbdb
commit 0893090a95
4 changed files with 74 additions and 91 deletions

View File

@@ -13,9 +13,15 @@ interface CodeProps {
blockId: string
subBlockId: string
isConnecting: boolean
placeholder?: string
}
export function Code({ blockId, subBlockId, isConnecting }: CodeProps) {
export function Code({
blockId,
subBlockId,
isConnecting,
placeholder = 'Write JavaScript...',
}: CodeProps) {
// State management
const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlockId)
const [code, setCode] = useState('')
@@ -201,7 +207,7 @@ export function Code({ blockId, subBlockId, isConnecting }: CodeProps) {
>
{code.length === 0 && (
<div className="absolute left-[42px] top-[12px] text-muted-foreground/50 select-none pointer-events-none">
Write JavaScript...
{placeholder}
</div>
)}

View File

@@ -18,39 +18,10 @@ interface TableRow {
export function Table({ columns, blockId, subBlockId }: TableProps) {
const [value, setValue] = useSubBlockValue(blockId, subBlockId)
const activePositionRef = useRef<{ rowIndex: number; column: string } | null>(null)
const inputRefs = useRef<Map<string, HTMLInputElement>>(new Map())
useEffect(() => {
if (activePositionRef.current && document.activeElement === document.body) {
const { rowIndex, column } = activePositionRef.current
const input = document.querySelector(
`input[data-row="${rowIndex}"][data-column="${column}"]`
) as HTMLInputElement
if (input) {
input.focus()
}
}
})
// Add new useEffect for handling input scrolling
useEffect(() => {
if (activePositionRef.current) {
const { rowIndex, column } = activePositionRef.current
const key = `${rowIndex}-${column}`
const input = inputRefs.current.get(key)
if (input) {
const scrollPosition = (input.selectionStart ?? 0) * 8
input.scrollLeft = scrollPosition - input.offsetWidth / 2
}
}
}, [value])
// Ensure value is properly typed and initialized
const rows = useMemo(() => {
if (!Array.isArray(value)) {
// Initialize with a single empty row if value is null or invalid
return [
{
id: crypto.randomUUID(),
@@ -71,7 +42,6 @@ export function Table({ columns, blockId, subBlockId }: TableProps) {
: row
)
// Add new row if typing in the last row
if (rowIndex === rows.length - 1 && value !== '') {
updatedRows.push({
id: crypto.randomUUID(),
@@ -83,73 +53,65 @@ export function Table({ columns, blockId, subBlockId }: TableProps) {
}
const handleDeleteRow = (rowIndex: number) => {
if (rows.length === 1) return // Don't delete if it's the last row
if (rows.length === 1) return
setValue(rows.filter((_, index) => index !== rowIndex))
}
const renderHeader = () => (
<thead>
<tr className="border-b">
{columns.map((column, index) => (
<th
key={column}
className={cn(
'px-4 py-2 text-left text-sm font-medium',
index < columns.length - 1 && 'border-r'
)}
>
{column}
</th>
))}
</tr>
</thead>
)
const renderCell = (row: TableRow, rowIndex: number, column: string, cellIndex: number) => (
<td
key={`${row.id}-${column}`}
className={cn('p-1', cellIndex < columns.length - 1 && 'border-r')}
>
<Input
value={row.cells[column] || ''}
placeholder={column}
onChange={(e) => handleCellChange(rowIndex, column, e.target.value)}
className="border-0 focus-visible:ring-0 focus-visible:ring-offset-0 text-muted-foreground placeholder:text-muted-foreground/50"
/>
</td>
)
const renderDeleteButton = (rowIndex: number) =>
rows.length > 1 && (
<td className="w-0 p-0">
<Button
variant="ghost"
size="icon"
className="opacity-0 group-hover:opacity-100 h-8 w-8 absolute right-2 top-1/2 -translate-y-1/2"
onClick={() => handleDeleteRow(rowIndex)}
>
<Trash2 className="h-4 w-4 text-muted-foreground" />
</Button>
</td>
)
return (
<div className="border rounded-md overflow-hidden">
<table className="w-full">
<thead>
<tr className="border-b">
{columns.map((column, index) => (
<th
key={column}
className={cn(
'px-4 py-2 text-left text-sm font-medium',
index < columns.length - 1 && 'border-r'
)}
>
{column}
</th>
))}
</tr>
</thead>
{renderHeader()}
<tbody>
{rows.map((row, rowIndex) => (
<tr key={row.id} className="border-t group relative">
{columns.map((column, cellIndex) => (
<td
key={`${row.id}-${column}`}
className={cn('p-1', cellIndex < columns.length - 1 && 'border-r')}
>
<Input
ref={(el) => {
if (el) {
inputRefs.current.set(`${rowIndex}-${column}`, el)
} else {
inputRefs.current.delete(`${rowIndex}-${column}`)
}
}}
data-row={rowIndex}
data-column={column}
value={row.cells[column] || ''}
placeholder={column}
onChange={(e) => handleCellChange(rowIndex, column, e.target.value)}
onFocus={() => {
activePositionRef.current = { rowIndex, column }
}}
onSelect={(e) => {
const input = e.currentTarget
const scrollPosition = (input.selectionStart ?? 0) * 8
input.scrollLeft = scrollPosition - input.offsetWidth / 2
}}
className="border-0 focus-visible:ring-0 focus-visible:ring-offset-0 text-muted-foreground placeholder:text-muted-foreground/50 allow-scroll"
/>
</td>
))}
{rows.length > 1 && (
<td className="w-0 p-0">
<Button
variant="ghost"
size="icon"
className="opacity-0 group-hover:opacity-100 h-8 w-8 absolute right-2 top-1/2 -translate-y-1/2"
onClick={() => handleDeleteRow(rowIndex)}
>
<Trash2 className="h-4 w-4 text-muted-foreground" />
</Button>
</td>
)}
{columns.map((column, cellIndex) => renderCell(row, rowIndex, column, cellIndex))}
{renderDeleteButton(rowIndex)}
</tr>
))}
</tbody>

View File

@@ -69,7 +69,14 @@ export function SubBlock({ blockId, config, isConnecting }: SubBlockProps) {
case 'table':
return <Table blockId={blockId} subBlockId={config.id} columns={config.columns ?? []} />
case 'code':
return <Code blockId={blockId} subBlockId={config.id} isConnecting={isConnecting} />
return (
<Code
blockId={blockId}
subBlockId={config.id}
isConnecting={isConnecting}
placeholder={config.placeholder}
/>
)
case 'switch':
return <Switch blockId={blockId} subBlockId={config.id} title={config.title ?? ''} />
case 'tool-input':

View File

@@ -24,6 +24,13 @@ export const ApiBlock: BlockConfig<RequestResponse> = {
layout: 'half',
options: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
},
{
id: 'params',
title: 'Query Params',
type: 'table',
layout: 'full',
columns: ['Key', 'Value'],
},
{
id: 'headers',
title: 'Headers',
@@ -36,6 +43,7 @@ export const ApiBlock: BlockConfig<RequestResponse> = {
title: 'Body',
type: 'code',
layout: 'full',
placeholder: 'Enter JSON...',
},
],
tools: {