Merging dev into main #49

Merged
8464960 merged 260 commits from dev into main 2026-01-28 10:48:52 +00:00
Showing only changes of commit ece94b0b02 - Show all commits

View File

@@ -1,137 +1,152 @@
import { useDraggable } from '@neodrag/react'; import {useDraggable} from '@neodrag/react';
import { import {
useReactFlow, useReactFlow,
type XYPosition type XYPosition
} from '@xyflow/react'; } from '@xyflow/react';
import { import {
type ReactNode, type ReactNode,
useCallback, useCallback,
useRef, useRef,
useState useState
} from 'react'; } from 'react';
// improve later to create better automatic IDs
let id = 0; let id = 0;
const getId = () => `dndnode_${id++}`; const getId = () => `dndnode_${id++}`;
interface DraggableNodeProps { interface DraggableNodeProps {
className?: string; className?: string;
children: ReactNode; children: ReactNode;
nodeType: string; nodeType: string;
onDrop: (nodeType: string, position: XYPosition) => void; onDrop: (nodeType: string, position: XYPosition) => void;
} }
function DraggableNode({ className, children, nodeType, onDrop }: DraggableNodeProps) { function DraggableNode({className, children, nodeType, onDrop}: DraggableNodeProps) {
const draggableRef = useRef<HTMLDivElement>(null); const draggableRef = useRef<HTMLDivElement>(null);
const [position, setPosition] = useState<XYPosition>({ x: 0, y: 0 }); const [position, setPosition] = useState<XYPosition>({x: 0, y: 0});
// @ts-ignore // @ts-ignore
useDraggable(draggableRef, { useDraggable(draggableRef, {
position: position, position: position,
onDrag: ({ offsetX, offsetY }) => { onDrag: ({offsetX, offsetY}) => {
// Calculate position relative to the viewport // Calculate position relative to the viewport
setPosition({ setPosition({
x: offsetX, x: offsetX,
y: offsetY, y: offsetY,
}); });
}, },
onDragEnd: ({ event }) => { onDragEnd: ({event}) => {
setPosition({ x: 0, y: 0 }); setPosition({x: 0, y: 0});
onDrop(nodeType, { onDrop(nodeType, {
x: event.clientX, x: event.clientX,
y: event.clientY, y: event.clientY,
}); });
}, },
}); });
return ( return (
<div className={className === "default" ? "draggable-node" : "draggable-node" + "__" + className} ref={draggableRef}> <div className={className === "default" ? "draggable-node" : "draggable-node" + "__" + className}
{children} ref={draggableRef}>
</div> {children}
); </div>
);
} }
export function Sidebar() { export function Sidebar() {
const { setNodes, screenToFlowPosition } = useReactFlow(); const {setNodes, screenToFlowPosition} = useReactFlow();
const handleNodeDrop = useCallback( const handleNodeDrop = useCallback(
(nodeType: string, screenPosition: XYPosition) => { (nodeType: string, screenPosition: XYPosition) => {
const flow = document.querySelector('.react-flow'); const flow = document.querySelector('.react-flow');
const flowRect = flow?.getBoundingClientRect(); const flowRect = flow?.getBoundingClientRect();
const isInFlow = const isInFlow =
flowRect && flowRect &&
screenPosition.x >= flowRect.left && screenPosition.x >= flowRect.left &&
screenPosition.x <= flowRect.right && screenPosition.x <= flowRect.right &&
screenPosition.y >= flowRect.top && screenPosition.y >= flowRect.top &&
screenPosition.y <= flowRect.bottom; screenPosition.y <= flowRect.bottom;
// Create a new node and add it to the flow // Create a new node and add it to the flow
if (isInFlow) { if (isInFlow) {
const position = screenToFlowPosition(screenPosition); const position = screenToFlowPosition(screenPosition);
const newNode = () => { const newNode = () => {
switch (nodeType) { switch (nodeType) {
case "phase": case "phase":
return { return {
id: getId(), id: getId(),
type: nodeType, type: nodeType,
position, position,
data: {label: `"new"`, number: (-1)}, data: {label: `"new"`, number: (-1)},
}; };
case "start": case "start":
return { return {
id: getId(), id: getId(),
type: nodeType, type: nodeType,
position, position,
data: {label: `new start node`}, data: {label: `new start node`},
}; };
case "end": case "end":
return { return {
id: getId(), id: getId(),
type: nodeType, type: nodeType,
position, position,
data: {label: `new end node`}, data: {label: `new end node`},
}; };
case "norm": case "norm":
return { return {
id: getId(), id: getId(),
type: nodeType, type: nodeType,
position, position,
data: {label: `new norm node`}, data: {label: `new norm node`},
}; };
default: { default: {
return { return {
id: getId(), id: getId(),
type: nodeType, type: nodeType,
position, position,
data: {label: `new default node`}, data: {label: `new default node`},
}; };
}
}
}
setNodes((nds) => nds.concat(newNode()));
} }
}, }
[setNodes, screenToFlowPosition], }
);
return ( setNodes((nds) => nds.concat(newNode()));
<aside style={{padding: '10px 10px',outline: '2.5pt solid black',borderRadius: '0pt 0pt 5pt 5pt', borderColor:'dimgrey', backgroundColor:'canvas', display:'flex', flexDirection: 'column', gap: '1rem'}}> }
<div className="description"> },
You can drag these nodes to the pane to create new nodes. [setNodes, screenToFlowPosition],
</div> );
<div className="DraggableNodeContainer" style={{backgroundColor:'canvas', display: 'flex', flexDirection: 'row', gap: '1rem', justifyContent: 'center'}}>
<DraggableNode className="phase" nodeType="phase" onDrop={handleNodeDrop}> return (
phase Node <aside style={{
</DraggableNode> padding: '10px 10px',
<DraggableNode className="norm" nodeType="norm" onDrop={handleNodeDrop}> outline: '2.5pt solid black',
norm Node borderRadius: '0pt 0pt 5pt 5pt',
</DraggableNode> borderColor: 'dimgrey',
</div> backgroundColor: 'canvas',
</aside> display: 'flex',
); flexDirection: 'column',
gap: '1rem'
}}>
<div className="description">
You can drag these nodes to the pane to create new nodes.
</div>
<div className="DraggableNodeContainer" style={{
backgroundColor: 'canvas',
display: 'flex',
flexDirection: 'row',
gap: '1rem',
justifyContent: 'center'
}}>
<DraggableNode className="phase" nodeType="phase" onDrop={handleNodeDrop}>
phase Node
</DraggableNode>
<DraggableNode className="norm" nodeType="norm" onDrop={handleNodeDrop}>
norm Node
</DraggableNode>
</div>
</aside>
);
} }