import React, { memo, useCallback, useContext, useMemo, useRef } from 'react' import CodeMirror from '@uiw/react-codemirror' import { javascript } from '@codemirror/lang-javascript' import FlowContext from '../../lib/flowContext' import { nodePropsAreEqual } from '../../lib/flowUtils' import { useResizeHeight } from '../../hooks/useResizeHeight' import { useTheme } from '../../lib/themeContext' import { BaseNode, BaseNodeContent, BaseNodeFooter, BaseNodeHeaderRow, } from './BaseNode' import { InputHandle, OutputHandle } from './NodeHandles' import { NodeFooterEdgeIndicators } from './NodeFooterEdgeIndicators' import { NodeHeaderTitle } from './NodeHeaderTitle' import { NodeMenubar } from './NodeMenubar' import { MenubarItem, MenubarSub, MenubarSubContent, MenubarSubTrigger } from '../ui/menubar' import { Code2, Variable } from 'lucide-react' type Props = { id: string data: { body?: string } width?: number height?: number } export const FunctionNode = memo(function FunctionNode({ id, data, width, height }: Props) { const bodyValue = data?.body ?? '' const { theme } = useTheme() const ctx = useContext(FlowContext) const setNodes = ctx?.setNodes const editorRef = useRef(null) const edges = ctx?.edges ?? [] const nodes = ctx?.nodes ?? [] const incomingEdges = useMemo(() => edges.filter((e: any) => e.target === id), [edges, id]) const incomingIds = useMemo(() => incomingEdges.map((e: any) => e.source).sort(), [incomingEdges]) const connectedVariableNodes = useMemo( () => (nodes as any[]).filter((n: any) => incomingIds.includes(n.id) && n.type === 'variable'), [nodes, incomingIds] ) const connectedFunctionNodes = useMemo( () => (nodes as any[]).filter((n: any) => incomingIds.includes(n.id) && n.type === 'function'), [nodes, incomingIds] ) const hasConnectedVariables = connectedVariableNodes.length > 0 const hasConnectedFunctions = connectedFunctionNodes.length > 0 const hasConnectedInputs = hasConnectedVariables || hasConnectedFunctions const onChange = useCallback( (val: string) => { if (setNodes) { setNodes((nds: any[]) => nds.map((n) => (n.id === id ? { ...n, data: { ...n.data, body: val } } : n)) ) } }, [id, setNodes] ) 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) => ( {n.id} insertVariableAtCursor(n)} > Insert at cursor ))} {connectedFunctionNodes.map((n: any) => ( {n.id} insertFunctionAtCursor(n)} > Insert at cursor ))} ) : undefined } />
{bodyValue ? `Javascript | ${bodyValue.length} chars` : 'none'}
) }, nodePropsAreEqual) FunctionNode.displayName = 'FunctionNode' export default FunctionNode