From ef5c1de961f331cd8ecccbd49c62dc26fcff119f Mon Sep 17 00:00:00 2001 From: stijnpotters Date: Fri, 29 May 2026 19:44:15 +0200 Subject: [PATCH 1/3] Add onSelect prop to ChildNode component for selection handling --- .../app/routes/studio/canvas/flow.tsx | 11 ++++++++ .../studio/canvas/nodetypes/child-node.tsx | 27 ++++++++++++++++--- .../studio/canvas/nodetypes/frank-node.tsx | 21 +++++++++++++++ 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/main/frontend/app/routes/studio/canvas/flow.tsx b/src/main/frontend/app/routes/studio/canvas/flow.tsx index 01967f94..60e79f75 100644 --- a/src/main/frontend/app/routes/studio/canvas/flow.tsx +++ b/src/main/frontend/app/routes/studio/canvas/flow.tsx @@ -825,6 +825,17 @@ function FlowCanvas({ onOpenInEditor }: { onOpenInEditor: () => void }) { const deleteSelection = useCallback((): boolean => { if (isEditing) return false + + const { parentId: storeParentId, nodeId: storeNodeId } = useNodeContextStore.getState() + if (storeParentId !== null) { + useFlowStore.getState().deleteChild(storeParentId, storeNodeId.toString()) + useNodeContextStore.getState().setParentId(null) + useNodeContextStore.getState().setChildParentId(null) + useNodeContextStore.getState().setNodeId(0) + showNodeContextMenu(false) + return true + } + const { nodes, edges, setNodes, setEdges } = useFlowStore.getState() const selectedNodeIds = new Set(nodes.filter((n) => n.selected).map((n) => n.id)) const hasSelection = selectedNodeIds.size > 0 || edges.some((e) => e.selected) diff --git a/src/main/frontend/app/routes/studio/canvas/nodetypes/child-node.tsx b/src/main/frontend/app/routes/studio/canvas/nodetypes/child-node.tsx index 5ea47114..2eb7d4a7 100644 --- a/src/main/frontend/app/routes/studio/canvas/nodetypes/child-node.tsx +++ b/src/main/frontend/app/routes/studio/canvas/nodetypes/child-node.tsx @@ -20,6 +20,7 @@ interface ChildNodeProperties { child: ChildNode gradientEnabled: boolean onEdit: (id: string) => void + onSelect: (id: string) => void parentId: string rootId: string } @@ -28,11 +29,23 @@ export function ChildNodeComponent({ child, gradientEnabled, onEdit, + onSelect, parentId, rootId, }: Readonly) { - const { setParentId, setChildParentId, setIsEditing, setDraggedName, draggedName, setNodeId, setAttributes } = - useNodeContextStore() + const { + setParentId, + setChildParentId, + setIsEditing, + setDraggedName, + draggedName, + setNodeId, + setAttributes, + nodeId, + parentId: selectedParentId, + isDirty, + } = useNodeContextStore() + const isSelected = nodeId === +child.id && selectedParentId !== null const showNodeContextMenu = useNodeContextMenu() const addChildToChild = useFlowStore((state) => state.addChildToChild) const [dragOver, setDragOver] = useState(false) @@ -158,12 +171,19 @@ export function ChildNodeComponent({ return (
{ + mouseEvent.stopPropagation() + if (isDirty) return + onSelect(child.id) + }} onDoubleClick={(event) => { event.stopPropagation() + if (isDirty) return onEdit(child.id) }} > @@ -203,6 +223,7 @@ export function ChildNodeComponent({ child={nested} gradientEnabled={gradientEnabled} onEdit={onEdit} + onSelect={onSelect} parentId={child.id} rootId={rootId} /> diff --git a/src/main/frontend/app/routes/studio/canvas/nodetypes/frank-node.tsx b/src/main/frontend/app/routes/studio/canvas/nodetypes/frank-node.tsx index 4350317e..0c2524c2 100644 --- a/src/main/frontend/app/routes/studio/canvas/nodetypes/frank-node.tsx +++ b/src/main/frontend/app/routes/studio/canvas/nodetypes/frank-node.tsx @@ -4,6 +4,7 @@ import { type NodeProps, NodeResizeControl, Position, + useReactFlow, useStore, useUpdateNodeInternals, } from '@xyflow/react' @@ -101,6 +102,7 @@ export default function FrankNode(properties: NodeProps) { const [mandatoryChildrenFulfilled, setMandatoryChildrenFulfilled] = useState(false) const [missingChildren, setMissingChildren] = useState([]) + const reactFlow = useReactFlow() const updateNodeInternals = useUpdateNodeInternals() const [isHandleMenuOpen, setIsHandleMenuOpen] = useState(false) const [handleMenuPosition, setHandleMenuPosition] = useState({ x: 0, y: 0 }) @@ -243,6 +245,24 @@ export default function FrankNode(properties: NodeProps) { setIsEditing(true) } + const selectChild = (childId: string) => { + const child = findChildRecursive(properties.data.children, childId) + if (!child) return + + const recordElements = elements as Record + const attributes = Object.values(recordElements).find((element) => element.name === child.subtype)?.attributes + + const isFirstLevel = properties.data.children.some((childNode) => childNode.id === childId) + setParentId(properties.id) + setChildParentId(isFirstLevel ? null : properties.id) + setNodeId(+childId) + setAttributes(attributes) + setEditingSubtype(child.subtype) + showNodeContextMenu(true) + + reactFlow.setNodes((nodes) => nodes.map((node) => ({ ...node, selected: false }))) + } + const changeHandleType = (handleIndex: number, newType: string) => { // Prevent changing to a duplicate handle type const existing = properties.data.sourceHandles.some( @@ -514,6 +534,7 @@ export default function FrankNode(properties: NodeProps) { child={child} gradientEnabled={gradientEnabled} onEdit={editChild} + onSelect={selectChild} parentId={properties.id} rootId={properties.id} /> From ec6c40d824aa885ff1944532826599d9913d1419 Mon Sep 17 00:00:00 2001 From: stijnpotters Date: Mon, 1 Jun 2026 09:58:22 +0200 Subject: [PATCH 2/3] Refactor drag-and-drop handling in ChildNode component for improved readability --- .../routes/projectlanding/project-landing.tsx | 2 +- .../studio/canvas/nodetypes/child-node.tsx | 30 +++++++------------ 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/src/main/frontend/app/routes/projectlanding/project-landing.tsx b/src/main/frontend/app/routes/projectlanding/project-landing.tsx index 70ca29a1..9e1029ce 100644 --- a/src/main/frontend/app/routes/projectlanding/project-landing.tsx +++ b/src/main/frontend/app/routes/projectlanding/project-landing.tsx @@ -6,7 +6,7 @@ import { fetchInstanceConfigurations, type FFConfiguration } from '~/services/fr import { useProjectStore } from '~/stores/project-store' import { ApiError } from '~/utils/api' import { logApiError } from '~/utils/logger' -import {getParentPath, normalizePath} from '~/utils/path-utils' +import { getParentPath, normalizePath } from '~/utils/path-utils' import ConfigurationRow from './configuration-row' import Search from '~/components/search/search' diff --git a/src/main/frontend/app/routes/studio/canvas/nodetypes/child-node.tsx b/src/main/frontend/app/routes/studio/canvas/nodetypes/child-node.tsx index 2eb7d4a7..c9a6905e 100644 --- a/src/main/frontend/app/routes/studio/canvas/nodetypes/child-node.tsx +++ b/src/main/frontend/app/routes/studio/canvas/nodetypes/child-node.tsx @@ -1,4 +1,5 @@ import { useCallback, useEffect, useMemo, useState } from 'react' +import clsx from 'clsx' import useFlowStore from '~/stores/flow-store' import { getElementTypeFromName } from '../../node-translator-module' import useNodeContextStore from '~/stores/node-context-store' @@ -79,12 +80,9 @@ export function ChildNodeComponent({ event.dataTransfer.dropEffect = allowed ? 'copy' : 'none' - if (!allowed && isThisNode) { - setDragForbidden(true) - setDragOver(false) - } else if (allowed && isThisNode) { - setDragForbidden(false) - setDragOver(true) + if (isThisNode) { + setDragForbidden(!allowed) + setDragOver(allowed) } } @@ -154,24 +152,18 @@ export function ChildNodeComponent({ ) useEffect(() => { - if (!draggedName) { - setCanDropDraggedElement(false) - return - } - - const allowed = canAcceptChild(draggedName) - - if (allowed) { - setCanDropDraggedElement(true) - return - } - setCanDropDraggedElement(false) + setCanDropDraggedElement(draggedName !== null && canAcceptChild(draggedName)) }, [draggedName, canAcceptChild, frankElement, child.subtype]) return (
Date: Mon, 1 Jun 2026 11:44:08 +0200 Subject: [PATCH 3/3] Refactor drag-and-drop handling in ChildNode component for improved readability --- src/main/frontend/app/routes/studio/canvas/flow.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/frontend/app/routes/studio/canvas/flow.tsx b/src/main/frontend/app/routes/studio/canvas/flow.tsx index 60e79f75..5ec31eb5 100644 --- a/src/main/frontend/app/routes/studio/canvas/flow.tsx +++ b/src/main/frontend/app/routes/studio/canvas/flow.tsx @@ -257,7 +257,6 @@ function FlowCanvas({ onOpenInEditor }: { onOpenInEditor: () => void }) { }, []) const { nodes, edges, onNodesChange, onEdgesChange, onConnect, onReconnect } = useFlowStore(useShallow(selector)) - const project = useProjectStore.getState().project const saveFlow = useCallback(async () => { const { nodes: flowNodes, edges: flowEdges, viewport: flowViewport } = useFlowStore.getState()