fix: canvas loop bug and node placement
All checks were successful
Deploy Nyla Frontend INT / build-and-deploy (push) Successful in 10m31s
All checks were successful
Deploy Nyla Frontend INT / build-and-deploy (push) Successful in 10m31s
This commit is contained in:
parent
90e1fbc40f
commit
fe7321c84d
2 changed files with 61 additions and 10 deletions
|
|
@ -158,6 +158,7 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
const [versions, setVersions] = useState<AutoVersion[]>([]);
|
const [versions, setVersions] = useState<AutoVersion[]>([]);
|
||||||
const [currentVersionId, setCurrentVersionId] = useState<string | null>(null);
|
const [currentVersionId, setCurrentVersionId] = useState<string | null>(null);
|
||||||
const [versionLoading, setVersionLoading] = useState(false);
|
const [versionLoading, setVersionLoading] = useState(false);
|
||||||
|
const didBootstrapEmptyCanvasRef = useRef(false);
|
||||||
|
|
||||||
const [targetFeatureInstanceId, setTargetFeatureInstanceId] = useState<string | null>(instanceId);
|
const [targetFeatureInstanceId, setTargetFeatureInstanceId] = useState<string | null>(instanceId);
|
||||||
|
|
||||||
|
|
@ -598,8 +599,22 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (loading || nodeTypes.length === 0) return;
|
if (loading || nodeTypes.length === 0) return;
|
||||||
if (currentWorkflowId || initialWorkflowId) return;
|
if (currentWorkflowId || initialWorkflowId) {
|
||||||
if (canvasNodes.length > 0) return;
|
didBootstrapEmptyCanvasRef.current = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (didBootstrapEmptyCanvasRef.current) return;
|
||||||
|
didBootstrapEmptyCanvasRef.current = true;
|
||||||
|
if (canvasNodes.length === 0 && canvasConnections.length === 0 && invocations.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.debug(`${LOG} bootstrapping empty canvas`, {
|
||||||
|
currentWorkflowId,
|
||||||
|
initialWorkflowId,
|
||||||
|
canvasNodes: canvasNodes.length,
|
||||||
|
canvasConnections: canvasConnections.length,
|
||||||
|
invocations: invocations.length,
|
||||||
|
});
|
||||||
applyGraphWithSync({ nodes: [], connections: [] }, [], {
|
applyGraphWithSync({ nodes: [], connections: [] }, [], {
|
||||||
skipHistory: true,
|
skipHistory: true,
|
||||||
});
|
});
|
||||||
|
|
@ -609,8 +624,9 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
currentWorkflowId,
|
currentWorkflowId,
|
||||||
initialWorkflowId,
|
initialWorkflowId,
|
||||||
canvasNodes.length,
|
canvasNodes.length,
|
||||||
|
canvasConnections.length,
|
||||||
|
invocations.length,
|
||||||
applyGraphWithSync,
|
applyGraphWithSync,
|
||||||
t,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const toggleCategory = useCallback((id: string) => {
|
const toggleCategory = useCallback((id: string) => {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ import { useLanguage } from '../../../providers/language/LanguageContext';
|
||||||
import { AiBadge } from '../nodes/shared/AiBadge';
|
import { AiBadge } from '../nodes/shared/AiBadge';
|
||||||
import { switchOutputLabel } from '../nodes/shared/graphUtils';
|
import { switchOutputLabel } from '../nodes/shared/graphUtils';
|
||||||
|
|
||||||
|
const LOG = '[FlowCanvas]';
|
||||||
|
|
||||||
export interface CanvasNode {
|
export interface CanvasNode {
|
||||||
id: string;
|
id: string;
|
||||||
type: string;
|
type: string;
|
||||||
|
|
@ -842,6 +844,8 @@ export const FlowCanvas = forwardRef<FlowCanvasHandle, FlowCanvasProps>(function
|
||||||
|
|
||||||
const onHistoryCheckpointRef = useRef(onHistoryCheckpoint);
|
const onHistoryCheckpointRef = useRef(onHistoryCheckpoint);
|
||||||
onHistoryCheckpointRef.current = onHistoryCheckpoint;
|
onHistoryCheckpointRef.current = onHistoryCheckpoint;
|
||||||
|
const onSelectionChangeRef = useRef(onSelectionChange);
|
||||||
|
onSelectionChangeRef.current = onSelectionChange;
|
||||||
|
|
||||||
const emitHistoryCheckpoint = useCallback(() => {
|
const emitHistoryCheckpoint = useCallback(() => {
|
||||||
onHistoryCheckpointRef.current?.();
|
onHistoryCheckpointRef.current?.();
|
||||||
|
|
@ -1019,12 +1023,19 @@ export const FlowCanvas = forwardRef<FlowCanvasHandle, FlowCanvasProps>(function
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const lastEmittedSelectionRef = useRef<{ nodeId: string | null; signature: string | null }>({
|
||||||
|
nodeId: null,
|
||||||
|
signature: null,
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (onSelectionChange) {
|
|
||||||
const node = selectedNodeId ? nodes.find((n) => n.id === selectedNodeId) ?? null : null;
|
const node = selectedNodeId ? nodes.find((n) => n.id === selectedNodeId) ?? null : null;
|
||||||
onSelectionChange(node);
|
const signature = node ? JSON.stringify(node) : null;
|
||||||
}
|
const last = lastEmittedSelectionRef.current;
|
||||||
}, [selectedNodeId, nodes, onSelectionChange]);
|
if (last.nodeId === selectedNodeId && last.signature === signature) return;
|
||||||
|
lastEmittedSelectionRef.current = { nodeId: selectedNodeId, signature };
|
||||||
|
onSelectionChangeRef.current?.(node);
|
||||||
|
}, [selectedNodeId, nodes]);
|
||||||
|
|
||||||
const handleConnectionClick = useCallback((e: React.MouseEvent, connId: string) => {
|
const handleConnectionClick = useCallback((e: React.MouseEvent, connId: string) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
@ -1088,6 +1099,11 @@ export const FlowCanvas = forwardRef<FlowCanvasHandle, FlowCanvasProps>(function
|
||||||
const handleDrop = useCallback(
|
const handleDrop = useCallback(
|
||||||
async (e: React.DragEvent) => {
|
async (e: React.DragEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
console.debug(`${LOG} drop received`, {
|
||||||
|
types: Array.from(e.dataTransfer.types),
|
||||||
|
clientX: e.clientX,
|
||||||
|
clientY: e.clientY,
|
||||||
|
});
|
||||||
// 1) externe Drop-Targets (z. B. ``application/json+workflow`` aus UDB-FilesTab)
|
// 1) externe Drop-Targets (z. B. ``application/json+workflow`` aus UDB-FilesTab)
|
||||||
if (onExternalDrop) {
|
if (onExternalDrop) {
|
||||||
const reservedMimes = new Set([
|
const reservedMimes = new Set([
|
||||||
|
|
@ -1113,16 +1129,35 @@ export const FlowCanvas = forwardRef<FlowCanvasHandle, FlowCanvasProps>(function
|
||||||
}
|
}
|
||||||
// 2) Standard: Node-Type aus der NodeSidebar
|
// 2) Standard: Node-Type aus der NodeSidebar
|
||||||
const raw = e.dataTransfer.getData('application/json');
|
const raw = e.dataTransfer.getData('application/json');
|
||||||
if (!raw || !containerRef.current) return;
|
if (!raw || !containerRef.current) {
|
||||||
|
console.debug(`${LOG} drop ignored`, {
|
||||||
|
hasRaw: Boolean(raw),
|
||||||
|
hasContainer: Boolean(containerRef.current),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const { type } = JSON.parse(raw);
|
const { type } = JSON.parse(raw);
|
||||||
const el = containerRef.current;
|
const el = containerRef.current;
|
||||||
const rect = el.getBoundingClientRect();
|
const rect = el.getBoundingClientRect();
|
||||||
const x = (e.clientX - rect.left - panOffset.x) / zoom - NODE_WIDTH / 2;
|
const x = (e.clientX - rect.left - panOffset.x) / zoom - NODE_WIDTH / 2;
|
||||||
const y = (e.clientY - rect.top - panOffset.y) / zoom - NODE_HEIGHT / 2;
|
const y = (e.clientY - rect.top - panOffset.y) / zoom - NODE_HEIGHT / 2;
|
||||||
|
console.debug(`${LOG} placing node from drop`, {
|
||||||
|
type,
|
||||||
|
raw,
|
||||||
|
dropX: x,
|
||||||
|
dropY: y,
|
||||||
|
panOffset,
|
||||||
|
zoom,
|
||||||
|
});
|
||||||
onDropNodeType(type, Math.max(0, x), Math.max(0, y));
|
onDropNodeType(type, Math.max(0, x), Math.max(0, y));
|
||||||
emitHistoryCheckpoint();
|
emitHistoryCheckpoint();
|
||||||
} catch (_) {}
|
} catch (error) {
|
||||||
|
console.debug(`${LOG} drop parse failed`, {
|
||||||
|
raw,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[onDropNodeType, onExternalDrop, panOffset, zoom, emitHistoryCheckpoint]
|
[onDropNodeType, onExternalDrop, panOffset, zoom, emitHistoryCheckpoint]
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue