import { useAppDispatch, useQuerybuilderGraph, useQuerybuilderHash } from '@graphpolaris/shared/lib/data-access'; import { useContext, useEffect, useMemo, useRef, useState } from 'react'; import { Handle, Position } from 'reactflow'; import { AllLogicTypes, Handles, LogicNodeAttributes, SchemaReactflowLogicNode, toHandleId } from '../../../model'; import { GeneralDescription, InputNode, InputNodeTypeTypes } from '../../../model/logic/general'; import { styleHandleMap } from '../../utils'; import { setQuerybuilderGraphology, toQuerybuilderGraphology } from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice'; import { LogicInput } from './LogicInput'; import { Button, Input, LogicPill } from '@graphpolaris/shared/lib/components'; import { QueryBuilderDispatcherContext } from '../../../panel/QueryBuilderDispatcher'; import { DropdownTrigger, DropdownContainer, DropdownItemContainer, DropdownItem } from '@graphpolaris/shared/lib/components/dropdowns'; export function QueryLogicPill(node: SchemaReactflowLogicNode) { const dispatch = useAppDispatch(); const data = node.data; const logic = data.logic; const { openLogicPillUpdate: openLogicPillChooser } = useContext(QueryBuilderDispatcherContext); const output = data.logic.output; const inputReference = useRef<HTMLInputElement>(null); const graph = useQuerybuilderGraph(); const graphologyHash = useQuerybuilderHash(); const graphologyGraph = useMemo(() => toQuerybuilderGraphology(graph), [graph]); const connectionsToLeft = useMemo(() => graph.edges.filter((edge) => edge.target === node.id), [graph]); const connectionsToRight = useMemo(() => graph.edges.filter((edge) => edge.source === node.id), [graph]); const graphologyNodeAttributes = useMemo<LogicNodeAttributes | undefined>( () => (graphologyGraph.hasNode(node.id) ? { ...(graphologyGraph.getNodeAttributes(node.id) as LogicNodeAttributes) } : undefined), [node.id], ); const [localInputCache, setLocalInputCache] = useState<Record<string, InputNodeTypeTypes>>({ ...graphologyNodeAttributes?.inputs }); const [openDropdown, setOpenDropdown] = useState(false); if (!data.id) throw new Error('LogicPill: data.id is undefined'); const defaultHandleData = { nodeId: data.id, nodeName: data.name, nodeType: data.type, }; const onInputUpdated = (value: string, input: InputNode, idx: number) => { let logicNode = { ...graphologyNodeAttributes }; if (!logicNode) throw new Error('LogicPill: logicNode is undefined'); let logicNodeInputs = { ...logicNode.inputs }; if (data.inputs[input.name] !== value) { logicNodeInputs[input.name] = value; logicNode.inputs = logicNodeInputs; graphologyGraph.setNodeAttribute<any>(node.id, 'inputs', logicNodeInputs); // FIXME: I'm not sure why TS requires <any> to work here dispatch(setQuerybuilderGraphology(graphologyGraph)); } }; function removeNode() { graphologyGraph.dropNode(node.id); dispatch(setQuerybuilderGraphology(graphologyGraph)); } useEffect(() => { if (inputReference?.current) inputReference.current.focus(); }, [node.id]); // FIXME: This is a temporary fix to prevent the logic pill from rendering when the input is not set if (!logic.input) { console.error('LogicPill: logic.input is undefined', logic.input); return null; } console.log('node', node); return ( <LogicPill title={ <div className="flex flex-row justify-between items-center"> <span>{connectionsToLeft[0]?.attributes?.sourceHandleData.attributeName}</span> <DropdownContainer> <DropdownTrigger size="md"> <Button variantType="secondary" variant="ghost" size="2xs" iconComponent={openDropdown ? 'icon-[ic--baseline-arrow-drop-up]' : 'icon-[ic--baseline-arrow-drop-down]'} className={openDropdown ? 'border-secondary-200' : ''} /> </DropdownTrigger> <DropdownItemContainer> <DropdownItem value="Remove" className="text-danger" onClick={(e) => removeNode()} /> </DropdownItemContainer> </DropdownContainer> </div> } handleLeft={ <Handle className={'!rounded-none !bg-transparent !w-full !h-full !border-0 !right-0 !left-0'} type="target" position={Position.Left} id={toHandleId({ ...defaultHandleData, attributeName: logic.input.name, attributeType: logic.input.type, handleType: Handles.LogicLeft, })} ></Handle> } > <div className={`py-1 h-fit border-[1px] border-secondary-200 ${data.selected ? 'bg-secondary-400' : 'bg-secondary-100'}`}> {/* <div className="m-1 mx-2 text-left">{output.name}</div> */} <DropdownTrigger popover={false} title={output.name} variant="ghost" size="xs" onClick={() => { openLogicPillChooser(node); }} /> {node.data.logic.inputs.map((input, i) => { const connection = connectionsToLeft.find( (edge) => edge?.attributes?.targetHandleData.nodeId === data.id && edge?.attributes?.targetHandleData.attributeName === input.name, ); return ( <div key={i}> <div className="w-full flex"> {!connection ? ( <LogicInput value={localInputCache?.[input.name] as string} type={input.type} onChange={(value: string) => { setLocalInputCache({ ...localInputCache, [input.name]: value }); }} onEnter={() => onInputUpdated(localInputCache?.[input.name] as string, input, i)} onBlur={() => onInputUpdated(localInputCache?.[input.name] as string, input, i)} /> ) : ( <span className="px-1 m-0 mx-1 p-0 h-5 w-full rounded-sm border-[1px]"> {connection?.attributes?.sourceHandleData.attributeName} </span> )} </div> <Handle type={'target'} position={Position.Left} id={toHandleId({ ...defaultHandleData, attributeName: input.name, attributeType: input.type, handleType: Handles.LogicLeft, })} key={input.name + input.type} style={{ top: `${((i + 1.05) / (node.data.logic.inputs.length + 0.4)) * 100}%` }} className={styleHandleMap[input.type] + ''} ></Handle> </div> ); })} {['upper_case', 'lower_case'].includes(node.data.logic.output.name) && ( <Handle type={'source'} position={Position.Right} id={toHandleId({ ...defaultHandleData, attributeName: output.name, attributeType: output.type, handleType: Handles.LogicRight, })} className={styleHandleMap?.[output.type]} ></Handle> )} </div> </LogicPill> ); }