|
|
|
@ -36,6 +36,9 @@ const LAYOUT_OPTIONS: { layout: GroupLayout; labelKey: TranslationKey }[] = [ |
|
|
|
]; |
|
|
|
|
|
|
|
const STACK_GAP = 20; |
|
|
|
const GROUP_MIN_WIDTH = 200; |
|
|
|
const GROUP_MIN_HEIGHT = 120; |
|
|
|
const GROUP_CHILD_PADDING = 20; |
|
|
|
|
|
|
|
function getNodeSize(node: FlowNode) { |
|
|
|
return { |
|
|
|
@ -177,6 +180,57 @@ export function GroupNode({ id, data, selected }: NodeProps<GroupNodeType>) { |
|
|
|
[edges, groupId, nodes] |
|
|
|
); |
|
|
|
|
|
|
|
const groupMemberNodes = useMemo( |
|
|
|
() => nodes.filter((node) => node.type !== "group" && (node.groupId === groupId || node.parentId === id)), |
|
|
|
[groupId, id, nodes] |
|
|
|
); |
|
|
|
|
|
|
|
const resizeBounds = useMemo(() => { |
|
|
|
let minWidth = GROUP_MIN_WIDTH; |
|
|
|
let minHeight = GROUP_MIN_HEIGHT; |
|
|
|
let maxRight = 0; |
|
|
|
let maxBottom = 0; |
|
|
|
|
|
|
|
groupMemberNodes.forEach((node) => { |
|
|
|
const { width, height } = getNodeSize(node); |
|
|
|
maxRight = Math.max(maxRight, node.position.x + width); |
|
|
|
maxBottom = Math.max(maxBottom, node.position.y + height); |
|
|
|
}); |
|
|
|
|
|
|
|
if (groupMemberNodes.length > 0) { |
|
|
|
minWidth = Math.max(minWidth, Math.ceil(maxRight + GROUP_CHILD_PADDING)); |
|
|
|
minHeight = Math.max(minHeight, Math.ceil(maxBottom + GROUP_CHILD_PADDING)); |
|
|
|
} |
|
|
|
|
|
|
|
return { minWidth, minHeight }; |
|
|
|
}, [groupMemberNodes]); |
|
|
|
|
|
|
|
const shouldResizeGroup = useCallback( |
|
|
|
(_event: unknown, params: { x: number; y: number; width: number; height: number }) => { |
|
|
|
if (!group) return true; |
|
|
|
if (groupMemberNodes.length === 0) return true; |
|
|
|
|
|
|
|
const right = params.x + params.width; |
|
|
|
const bottom = params.y + params.height; |
|
|
|
|
|
|
|
return groupMemberNodes.every((node) => { |
|
|
|
const { width, height } = getNodeSize(node); |
|
|
|
const nodeLeft = group.position.x + node.position.x; |
|
|
|
const nodeTop = group.position.y + node.position.y; |
|
|
|
const nodeRight = nodeLeft + width; |
|
|
|
const nodeBottom = nodeTop + height; |
|
|
|
|
|
|
|
return ( |
|
|
|
nodeLeft >= params.x && |
|
|
|
nodeTop >= params.y && |
|
|
|
nodeRight <= right && |
|
|
|
nodeBottom <= bottom |
|
|
|
); |
|
|
|
}); |
|
|
|
}, |
|
|
|
[group, groupMemberNodes] |
|
|
|
); |
|
|
|
|
|
|
|
const handleBatchDownload = useCallback( |
|
|
|
async (event: React.MouseEvent) => { |
|
|
|
event.stopPropagation(); |
|
|
|
@ -373,15 +427,17 @@ export function GroupNode({ id, data, selected }: NodeProps<GroupNodeType>) { |
|
|
|
|
|
|
|
<NodeResizer |
|
|
|
isVisible={showResizeControls} |
|
|
|
minWidth={200} |
|
|
|
minHeight={120} |
|
|
|
minWidth={resizeBounds.minWidth} |
|
|
|
minHeight={resizeBounds.minHeight} |
|
|
|
shouldResize={shouldResizeGroup} |
|
|
|
lineClassName="!border-neutral-400/40" |
|
|
|
handleClassName="!h-5 !w-5 !border-none !bg-transparent !p-0" |
|
|
|
/> |
|
|
|
<NodeResizeControl |
|
|
|
position="bottom-right" |
|
|
|
minWidth={200} |
|
|
|
minHeight={120} |
|
|
|
minWidth={resizeBounds.minWidth} |
|
|
|
minHeight={resizeBounds.minHeight} |
|
|
|
shouldResize={shouldResizeGroup} |
|
|
|
className="group-resize-corner-control nodrag nopan nowheel" |
|
|
|
> |
|
|
|
<div |
|
|
|
|