import React, { memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react' import { autocompletion } from '@codemirror/autocomplete' import CodeMirror from '@uiw/react-codemirror' import FlowContext from '../../lib/flowContext' import { useResizeHeight } from '../../hooks/useResizeHeight' import { nunjucksCompletionSource } from '../../lib/nunjucksAutocomplete' import { plantumlLanguage } from '../../lib/plantumlLanguage' import { useTheme } from '../../lib/themeContext' import { BaseNode, BaseNodeContent, BaseNodeFooter, BaseNodeFooterText, BaseNodeHeaderRow, } from './BaseNode' import { GitBranchPlus, ScrollText } from 'lucide-react' import { Menubar, MenubarContent, MenubarItem, MenubarMenu, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, } from '../ui/menubar' import { InputHandle, OutputHandle } from './NodeHandles' type Props = { id: string data: any width?: number height?: number } export const ConfigNode = memo(function ConfigNode({ id, data, width, height }: Props) { const [value, setValue] = useState(data?.plantuml ?? '@startuml\n\n@enduml\n') const { theme } = useTheme() const ctx = useContext(FlowContext) const setNodes = ctx?.setNodes const storedPlantuml = ctx?.nodes?.find((n: any) => n.id === id)?.data?.plantuml ?? '' const editorRef = useRef(null) const incomingEdges = ctx?.edges?.filter((e: any) => e.target === id) ?? [] const incomingIds = incomingEdges.map((e: any) => e.source).sort() const connectedConfigNodes = (ctx?.nodes ?? []).filter((n: any) => incomingIds.includes(n.id) && n.type === 'config') const connectedVariableNodes = (ctx?.nodes ?? []).filter((n: any) => incomingIds.includes(n.id) && n.type === 'variable') const connectedFunctionNodes = (ctx?.nodes ?? []).filter((n: any) => incomingIds.includes(n.id) && n.type === 'function') const hasDependencies = connectedConfigNodes.length > 0 || connectedVariableNodes.length > 0 || connectedFunctionNodes.length > 0 useEffect(() => { if (setNodes) { setNodes((nds: any[]) => nds.map((n) => (n.id === id ? { ...n, data: { ...n.data, plantuml: value } } : n))) } // eslint-disable-next-line react-hooks/exhaustive-deps }, []) const onChange = useCallback( (val: string) => { setValue(val) if (setNodes) { setNodes((nds: any[]) => nds.map((n) => (n.id === id ? { ...n, data: { ...n.data, plantuml: val } } : n))) } }, [id, setNodes] ) const insertAt = useCallback( (insertText: string, mode: 'prepend' | 'append' | 'cursor') => { const ref = editorRef.current as { view: { state: { doc: { length: number; toString(): string }; 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 { const main = view.state.selection.main from = main.from } view.dispatch({ changes: { from, to: from, insert: insertText } }) const newVal = view.state.doc.toString() onChange(newVal) return } if (mode === 'prepend') { onChange(insertText + value) } else { onChange(value + insertText) } }, [onChange, value] ) const insertIncludeFromNode = useCallback( (sourceNode: any, mode: 'prepend' | 'append' | 'cursor') => { const ref = `${sourceNode.id}.puml` insertAt(`!include ${ref}\n`, mode) }, [insertAt] ) const insertVariableReference = useCallback( (sourceNode: any, mode: 'prepend' | 'append' | 'cursor') => { insertAt(`\${${sourceNode.id}}`, mode) }, [insertAt] ) const insertFunctionCall = useCallback( (sourceNode: any, mode: 'prepend' | 'append' | 'cursor') => { insertAt(`\${${sourceNode.id}()}`, mode) }, [insertAt] ) const variableIds = useMemo(() => connectedVariableNodes.map((n: any) => n.id), [connectedVariableNodes]) const functionIds = useMemo(() => connectedFunctionNodes.map((n: any) => n.id), [connectedFunctionNodes]) const configTitles = useMemo( () => connectedConfigNodes.map((n: any) => n.data?.title ?? n.id), [connectedConfigNodes], ) const extensions = useMemo( () => [ plantumlLanguage.extension, autocompletion({ override: [nunjucksCompletionSource(variableIds, configTitles, functionIds)], activateOnTyping: true, }), ], [variableIds, functionIds, configTitles], ) const [editorHeight, editorContainerRef] = useResizeHeight(180) const dimensions = width != null && height != null && width > 0 && height > 0 ? { width, height } : undefined return ( }> } title={`${id}.puml`} /> {hasDependencies && (
{connectedConfigNodes.map((n: any) => ( {n.data?.title ?? n.id} insertIncludeFromNode(n, 'prepend')} > Prepend include insertIncludeFromNode(n, 'append')} > Append include insertIncludeFromNode(n, 'cursor')} > Insert at cursor ))} {connectedVariableNodes.map((n: any) => ( {n.id} insertVariableReference(n, 'cursor')} > Insert at cursor ))} {connectedFunctionNodes.map((n: any) => ( {n.id} insertFunctionCall(n, 'cursor')} > Insert at cursor ))}
)}
{storedPlantuml ? `${storedPlantuml.length} chars` : 'none'}
) }) ConfigNode.displayName = 'ConfigNode' export default ConfigNode