import React, { useCallback, useMemo, useRef } from 'react' import CodeMirror from '@uiw/react-codemirror' import { javascript } from '@codemirror/lang-javascript' import { AbstractNodeProps, createAbstractNodeComponent, useAbstractNode, type FlowNode, } from '../../lib/abstractNode' import { useResizeHeight } from '../../hooks/useResizeHeight' import { useTheme } from '../../lib/themeContext' import { BaseNode, BaseNodeContent, BaseNodeFooter, BaseNodeHeaderRow, } from '../base/BaseNode' import { InputHandle, OutputHandle } from '../base/NodeHandles' import { NodeFooterEdgeIndicators } from '../base/NodeFooterEdgeIndicators' import { NodeHeaderTitle } from '../base/NodeHeaderTitle' import { NodeMenubar } from '../base/NodeMenubar' import { MenubarItem, MenubarShortcut } from '../ui/menubar' import { Kbd } from '../ui/kbd' import { Code2, Variable } from 'lucide-react' export type FunctionNodeData = { body?: string } type Props = AbstractNodeProps function FunctionNodeComponent({ id, data, width, height }: Props) { const bodyValue = data?.body ?? '' const { theme } = useTheme() const { nodes, sourceIds, updateData } = useAbstractNode(id, data ?? {}) const editorRef = useRef(null) const connectedVariableNodes = useMemo( () => (nodes as FlowNode[]).filter((n) => sourceIds.includes(n.id) && n.type === 'variable'), [nodes, sourceIds] ) const connectedFunctionNodes = useMemo( () => (nodes as FlowNode[]).filter((n) => sourceIds.includes(n.id) && n.type === 'function'), [nodes, sourceIds] ) const hasConnectedInputs = connectedVariableNodes.length > 0 || connectedFunctionNodes.length > 0 const onChange = useCallback( (val: string) => updateData({ body: val }), [updateData] ) const insertAt = useCallback( (insertText: string, mode: 'prepend' | 'append' | 'cursor') => { const ref = editorRef.current as { view: { state: { doc: { length: number }; selection: { main: { from: number } } }; dispatch: (arg: { changes: { from: number; to: number; insert: string } }) => void } } | null if (ref?.view) { const view = ref.view const doc = view.state.doc const len = doc.length let from: number if (mode === 'prepend') from = 0 else if (mode === 'append') from = len else from = view.state.selection.main.from view.dispatch({ changes: { from, to: from, insert: insertText } }) onChange(view.state.doc.toString()) return } if (mode === 'prepend') onChange(insertText + bodyValue) else onChange(bodyValue + insertText) }, [onChange, bodyValue] ) const insertVariableAtCursor = useCallback( (variableNode: any) => { insertAt(variableNode.id, 'cursor') }, [insertAt] ) const insertFunctionAtCursor = useCallback( (functionNode: any) => { insertAt(functionNode.id, 'cursor') }, [insertAt] ) const extensions = useMemo(() => [javascript()], []) const [editorHeight, editorContainerRef] = useResizeHeight(120) const dimensions = width != null && height != null && width > 0 && height > 0 ? { width, height } : undefined return ( }> } title={} />
{connectedVariableNodes.map((n: any) => ( insertVariableAtCursor(n)} > {n.id} Insert ))} {connectedFunctionNodes.map((n: any) => ( insertFunctionAtCursor(n)} > {n.id} Insert ))} ) : ( Connect nodes to insert at cursor ) } />
{bodyValue ? `JavaScript · ${bodyValue.length} chars` : 'JavaScript · none'}
) } export const FunctionNode = createAbstractNodeComponent( 'FunctionNode', FunctionNodeComponent ) export default FunctionNode