Merge branch 'hotfix/v0.2.10' into develop
This commit is contained in:
@@ -149,8 +149,8 @@ const Editor: FC<LexicalEditorProps> =({
|
||||
<HistoryPlugin />
|
||||
<CommandPlugin />
|
||||
<AutocompletePlugin options={options} />
|
||||
<CharacterCountPlugin setCount={setCount} onChange={onChange} />
|
||||
<InitialValuePlugin value={value} options={options} />
|
||||
<CharacterCountPlugin setCount={setCount} />
|
||||
<InitialValuePlugin value={value} options={options} onChange={onChange} />
|
||||
<BlurPlugin />
|
||||
</div>
|
||||
</LexicalComposer>
|
||||
|
||||
@@ -1,41 +1,22 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { $getRoot, $isParagraphNode } from 'lexical';
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
||||
|
||||
import { $isVariableNode } from '../nodes/VariableNode';
|
||||
|
||||
const CharacterCountPlugin = ({ setCount, onChange }: { setCount: (count: number) => void; onChange?: (value: string) => void }) => {
|
||||
const CharacterCountPlugin = ({ setCount }: { setCount: (count: number) => void }) => {
|
||||
const [editor] = useLexicalComposerContext();
|
||||
const onChangeRef = useRef(onChange);
|
||||
onChangeRef.current = onChange;
|
||||
|
||||
useEffect(() => {
|
||||
return editor.registerUpdateListener(({ editorState, tags }) => {
|
||||
if (tags.has('programmatic')) return;
|
||||
editorState.read(() => {
|
||||
const root = $getRoot();
|
||||
let serializedContent = '';
|
||||
|
||||
// Traverse all nodes and serialize properly
|
||||
const paragraphs: string[] = [];
|
||||
root.getChildren().forEach(child => {
|
||||
if ($isParagraphNode(child)) {
|
||||
let paragraphContent = '';
|
||||
child.getChildren().forEach(node => {
|
||||
if ($isVariableNode(node)) {
|
||||
paragraphContent += node.getTextContent();
|
||||
} else {
|
||||
paragraphContent += node.getTextContent();
|
||||
}
|
||||
});
|
||||
paragraphs.push(paragraphContent);
|
||||
paragraphs.push(child.getChildren().map(n => n.getTextContent()).join(''));
|
||||
}
|
||||
});
|
||||
|
||||
serializedContent = paragraphs.join('\n');
|
||||
|
||||
setCount(serializedContent.length);
|
||||
onChangeRef.current?.(serializedContent);
|
||||
setCount(paragraphs.join('\n').length);
|
||||
});
|
||||
});
|
||||
}, [editor, setCount]);
|
||||
|
||||
@@ -50,7 +50,7 @@ const CommandPlugin = () => {
|
||||
|
||||
// Create and insert the variable node
|
||||
const tagNode = $createVariableNode(payload.data);
|
||||
const spaceNode = $createTextNode(' ');
|
||||
const spaceNode = $createTextNode('');
|
||||
|
||||
anchorNode.insertAfter(tagNode);
|
||||
tagNode.insertAfter(spaceNode);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
||||
import { $getRoot, $createParagraphNode, $createTextNode } from 'lexical';
|
||||
import { $getRoot, $createParagraphNode, $createTextNode, $isParagraphNode } from 'lexical';
|
||||
|
||||
import { $createVariableNode } from '../nodes/VariableNode';
|
||||
import { type Suggestion } from '../plugin/AutocompletePlugin'
|
||||
@@ -14,24 +14,34 @@ import { type Suggestion } from '../plugin/AutocompletePlugin'
|
||||
interface InitialValuePluginProps {
|
||||
value: string;
|
||||
options?: Suggestion[];
|
||||
onChange?: (value: string) => void;
|
||||
}
|
||||
|
||||
const InitialValuePlugin: React.FC<InitialValuePluginProps> = ({ value, options = [] }) => {
|
||||
const InitialValuePlugin: React.FC<InitialValuePluginProps> = ({ value, options = [], onChange }) => {
|
||||
const [editor] = useLexicalComposerContext();
|
||||
const prevValueRef = useRef<string>('');
|
||||
const isUserInputRef = useRef(false);
|
||||
const optionsRef = useRef(options);
|
||||
optionsRef.current = options;
|
||||
const onChangeRef = useRef(onChange);
|
||||
onChangeRef.current = onChange;
|
||||
|
||||
useEffect(() => {
|
||||
return editor.registerUpdateListener(({ editorState, tags }) => {
|
||||
if (tags.has('programmatic')) return;
|
||||
editorState.read(() => {
|
||||
const root = $getRoot();
|
||||
const textContent = root.getTextContent();
|
||||
if (textContent !== prevValueRef.current) {
|
||||
const paragraphs: string[] = [];
|
||||
root.getChildren().forEach(child => {
|
||||
if ($isParagraphNode(child)) {
|
||||
paragraphs.push(child.getChildren().map(n => n.getTextContent()).join(''));
|
||||
}
|
||||
});
|
||||
const text = paragraphs.join('\n');
|
||||
if (text !== prevValueRef.current) {
|
||||
isUserInputRef.current = true;
|
||||
prevValueRef.current = textContent;
|
||||
prevValueRef.current = text;
|
||||
onChangeRef.current?.(text);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -49,23 +49,24 @@ const AddNode: ReactShapeConfig['component'] = ({ node, graph }) => {
|
||||
|
||||
const incomingEdges = graph.getIncomingEdges(node);
|
||||
const outgoingEdges = graph.getOutgoingEdges(node);
|
||||
|
||||
incomingEdges?.forEach(edge => {
|
||||
graph.addEdge({
|
||||
const addedEdges: any[] = [];
|
||||
|
||||
incomingEdges?.forEach((edge: any) => {
|
||||
addedEdges.push(graph.addEdge({
|
||||
source: { cell: edge.getSourceCellId(), port: edge.getSourcePortId() },
|
||||
target: { cell: newNode.id, port: newNode.getPorts().find((port: any) => port.group === 'left')?.id || 'left' },
|
||||
...edgeAttrs
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
outgoingEdges?.forEach(edge => {
|
||||
outgoingEdges?.forEach((edge: any) => {
|
||||
const targetCell = graph.getCellById(edge.getTargetCellId()) as any;
|
||||
const targetPortId = targetCell?.getPorts?.()?.find((port: any) => port.group === 'left')?.id || edge.getTargetPortId();
|
||||
graph.addEdge({
|
||||
addedEdges.push(graph.addEdge({
|
||||
source: { cell: newNode.id, port: newNode.getPorts().find((port: any) => port.group === 'right')?.id || 'right' },
|
||||
target: { cell: edge.getTargetCellId(), port: targetPortId },
|
||||
...edgeAttrs
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
// Remove all add-node type nodes
|
||||
@@ -75,6 +76,15 @@ const AddNode: ReactShapeConfig['component'] = ({ node, graph }) => {
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
addedEdges.forEach(e => {
|
||||
const src = graph.getCellById(e.getSourceCellId());
|
||||
const tgt = graph.getCellById(e.getTargetCellId());
|
||||
if (src?.isNode()) src.toFront();
|
||||
if (tgt?.isNode()) tgt.toFront();
|
||||
});
|
||||
}, 50);
|
||||
|
||||
// Automatically adjust loop node size
|
||||
const loopNode = graph.getNodes().find((n: any) => n.getData()?.id === cycleId);
|
||||
if (loopNode) {
|
||||
|
||||
@@ -59,6 +59,9 @@ const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => {
|
||||
target: { cell: addNode.id, port: targetPort },
|
||||
...edgeAttrs,
|
||||
});
|
||||
|
||||
cycleStartNode.toFront()
|
||||
addNode.toFront()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,6 +120,12 @@ const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => {
|
||||
...edgeAttrs
|
||||
}
|
||||
graph.addEdge(edgeConfig)
|
||||
|
||||
setTimeout(() => {
|
||||
|
||||
cycleStartNode.toFront()
|
||||
addNode.toFront()
|
||||
}, 0)
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -34,9 +34,12 @@ const PortClickHandler: React.FC<PortClickHandlerProps> = ({ graph }) => {
|
||||
};
|
||||
|
||||
window.addEventListener('port:click', handlePortClick as EventListener);
|
||||
const handleBlankClick = () => handlePopoverClose();
|
||||
window.addEventListener('blank:click', handleBlankClick);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('port:click', handlePortClick as EventListener);
|
||||
window.removeEventListener('blank:click', handleBlankClick);
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -188,38 +191,39 @@ const PortClickHandler: React.FC<PortClickHandlerProps> = ({ graph }) => {
|
||||
setTimeout(() => {
|
||||
const newPorts = newNode.getPorts();
|
||||
|
||||
const addedEdges: any[] = [];
|
||||
if (edgeInsertion) {
|
||||
// Edge insertion: create source→new and new→target edges
|
||||
const { targetCell, targetPort: origTargetPort } = edgeInsertion;
|
||||
const newLeftPort = newPorts.find((p: any) => p.group === 'left')?.id || 'left';
|
||||
const newRightPort = newPorts.find((p: any) => p.group === 'right')?.id || 'right';
|
||||
graph.addEdge({
|
||||
addedEdges.push(graph.addEdge({
|
||||
source: { cell: sourceNode.id, port: sourcePort },
|
||||
target: { cell: newNode.id, port: newLeftPort },
|
||||
...edgeAttrs
|
||||
});
|
||||
graph.addEdge({
|
||||
}));
|
||||
addedEdges.push(graph.addEdge({
|
||||
source: { cell: newNode.id, port: newRightPort },
|
||||
target: { cell: targetCell.id, port: origTargetPort },
|
||||
...edgeAttrs
|
||||
});
|
||||
}));
|
||||
setEdgeInsertion(null);
|
||||
} else if (sourcePortGroup === 'left') {
|
||||
// Connect from left port to new node's right side
|
||||
const targetPort = newPorts.find((port: any) => port.group === 'right')?.id || 'right';
|
||||
graph.addEdge({
|
||||
addedEdges.push(graph.addEdge({
|
||||
source: { cell: newNode.id, port: targetPort },
|
||||
target: { cell: sourceNode.id, port: sourcePort },
|
||||
...edgeAttrs
|
||||
});
|
||||
}));
|
||||
} else {
|
||||
// Connect from right port to new node's left side
|
||||
const targetPort = newPorts.find((port: any) => port.group === 'left')?.id || 'left';
|
||||
graph.addEdge({
|
||||
addedEdges.push(graph.addEdge({
|
||||
source: { cell: sourceNode.id, port: sourcePort },
|
||||
target: { cell: newNode.id, port: targetPort },
|
||||
...edgeAttrs
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
// Adjust loop node size when child node is added via port within loop node
|
||||
@@ -266,6 +270,44 @@ const PortClickHandler: React.FC<PortClickHandlerProps> = ({ graph }) => {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const isCycleContainer = (type: string) => type === 'loop' || type === 'iteration';
|
||||
const newNodeType = selectedNodeType.type;
|
||||
|
||||
// Helper: bring all child nodes and their edges of a cycle container to front
|
||||
const bringCycleChildrenToFront = (cycleContainerId: string) => {
|
||||
|
||||
graph.getEdges().forEach((e: any) => {
|
||||
const src = graph.getCellById(e.getSourceCellId());
|
||||
const tgt = graph.getCellById(e.getTargetCellId());
|
||||
if (src?.getData()?.cycle === cycleContainerId || tgt?.getData()?.cycle === cycleContainerId) e.toFront();
|
||||
});
|
||||
graph.getNodes().forEach((n: any) => {
|
||||
if (n.getData()?.cycle === cycleContainerId) n.toFront();
|
||||
});
|
||||
};
|
||||
|
||||
if (isCycleContainer(sourceNodeType)) {
|
||||
console.log('isCycleContainer(sourceNodeType)')
|
||||
// Case 4: source is a loop/iteration node — bring new node to front, then its children
|
||||
newNode.toFront();
|
||||
sourceNode.toFront();
|
||||
bringCycleChildrenToFront(sourceNodeData.id);
|
||||
} else if (isCycleContainer(newNodeType)) {
|
||||
console.log('isCycleContainer(newNodeType)')
|
||||
// Case 3: adding a loop/iteration node from a normal node — bring new node to front, then its children
|
||||
newNode.toFront();
|
||||
sourceNode.toFront()
|
||||
bringCycleChildrenToFront(id);
|
||||
} else {
|
||||
// Case 2: normal node → normal node
|
||||
addedEdges.forEach(e => {
|
||||
const src = graph.getCellById(e.getSourceCellId());
|
||||
const tgt = graph.getCellById(e.getTargetCellId());
|
||||
if (src?.isNode()) src.toFront();
|
||||
if (tgt?.isNode()) tgt.toFront();
|
||||
});
|
||||
}
|
||||
}, 50);
|
||||
|
||||
// Clean up temporary element
|
||||
|
||||
@@ -61,6 +61,20 @@ const CaseList: FC<CaseListProps> = ({
|
||||
const { t } = useTranslation();
|
||||
const form = Form.useFormInstance();
|
||||
|
||||
const bringLoopChildrenToFront = (cell: any) => {
|
||||
const type = cell?.getData()?.type;
|
||||
if ((type !== 'loop' && type !== 'iteration') || !graphRef?.current) return;
|
||||
const cycleId = cell.getData().id;
|
||||
graphRef.current.getEdges().forEach((edge: any) => {
|
||||
const src = graphRef.current?.getCellById(edge.getSourceCellId());
|
||||
const tgt = graphRef.current?.getCellById(edge.getTargetCellId());
|
||||
if (src?.getData()?.cycle === cycleId || tgt?.getData()?.cycle === cycleId) edge.toFront();
|
||||
});
|
||||
graphRef.current.getNodes().forEach((n: any) => {
|
||||
if (n.getData()?.cycle === cycleId) n.toFront();
|
||||
});
|
||||
};
|
||||
|
||||
// Recalculate node height and port Y positions without rebuilding ports
|
||||
const updateNodeLayout = (cases: any[]) => {
|
||||
if (!selectedNode || !graphRef?.current) return;
|
||||
@@ -139,6 +153,10 @@ const CaseList: FC<CaseListProps> = ({
|
||||
...edgeAttrs,
|
||||
});
|
||||
}
|
||||
sourceCell.toFront()
|
||||
selectedNode.toFront()
|
||||
bringLoopChildrenToFront(sourceCell)
|
||||
bringLoopChildrenToFront(selectedNode)
|
||||
graphRef.current?.removeCell(edge);
|
||||
return;
|
||||
}
|
||||
@@ -183,6 +201,10 @@ const CaseList: FC<CaseListProps> = ({
|
||||
target: { cell: targetCellId, port: targetPortId },
|
||||
...edgeAttrs
|
||||
});
|
||||
selectedNode.toFront()
|
||||
bringLoopChildrenToFront(selectedNode)
|
||||
targetCell.toFront()
|
||||
bringLoopChildrenToFront(targetCell)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,20 @@ const CategoryList: FC<CategoryListProps> = ({ parentName, selectedNode, graphRe
|
||||
const form = Form.useFormInstance();
|
||||
const formValues = Form.useWatch([parentName], form);
|
||||
|
||||
const bringLoopChildrenToFront = (cell: any) => {
|
||||
const type = cell?.getData()?.type;
|
||||
if ((type !== 'loop' && type !== 'iteration') || !graphRef?.current) return;
|
||||
const cycleId = cell.getData().id;
|
||||
graphRef.current.getEdges().forEach((edge: any) => {
|
||||
const src = graphRef.current?.getCellById(edge.getSourceCellId());
|
||||
const tgt = graphRef.current?.getCellById(edge.getTargetCellId());
|
||||
if (src?.getData()?.cycle === cycleId || tgt?.getData()?.cycle === cycleId) edge.toFront();
|
||||
});
|
||||
graphRef.current.getNodes().forEach((n: any) => {
|
||||
if (n.getData()?.cycle === cycleId) n.toFront();
|
||||
});
|
||||
};
|
||||
|
||||
// Update node ports based on category count changes (add/remove categories)
|
||||
const updateNodePorts = (caseCount: number, removedCaseIndex?: number) => {
|
||||
if (!selectedNode || !graphRef?.current) return;
|
||||
@@ -88,6 +102,10 @@ const CategoryList: FC<CategoryListProps> = ({ parentName, selectedNode, graphRe
|
||||
target: { cell: selectedNode.id, port: targetPortId },
|
||||
...edgeAttrs
|
||||
});
|
||||
sourceCell.toFront()
|
||||
bringLoopChildrenToFront(sourceCell)
|
||||
selectedNode.toFront()
|
||||
bringLoopChildrenToFront(selectedNode)
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -119,6 +137,10 @@ const CategoryList: FC<CategoryListProps> = ({ parentName, selectedNode, graphRe
|
||||
target: { cell: targetCellId, port: targetPortId },
|
||||
...edgeAttrs
|
||||
});
|
||||
selectedNode.toFront()
|
||||
bringLoopChildrenToFront(selectedNode)
|
||||
targetCell.toFront()
|
||||
bringLoopChildrenToFront(targetCell)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ interface KnowledgeConfigModalProps {
|
||||
refresh: (values: KnowledgeConfigForm, type: 'knowledgeConfig') => void;
|
||||
}
|
||||
const retrieveTypes: RetrieveType[] = ['participle', 'semantic', 'hybrid',
|
||||
// 'graph'
|
||||
'graph'
|
||||
]
|
||||
|
||||
const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfigModalProps>(({
|
||||
|
||||
Reference in New Issue
Block a user