|
1 | 1 | import { isNil, isArrayNotEmpty, isNotNil, toID, isFalse } from '@utils/index' |
2 | 2 | import clsx from 'clsx' |
3 | | -import { useMemo, useCallback, useState } from 'react' |
| 3 | +import { useMemo, useCallback, useState, useRef } from 'react' |
4 | 4 | import { ModelType } from '@api/client' |
5 | 5 | import { useLineageFlow } from './context' |
6 | 6 | import { type GraphNodeData } from './help' |
7 | | -import { Position, type NodeProps } from 'reactflow' |
| 7 | +import { Position, type NodeProps, NodeResizeControl } from 'reactflow' |
| 8 | +import '@reactflow/node-resizer/dist/style.css' |
8 | 9 | import { type Column } from '@api/client' |
9 | 10 | import ModelNodeHeaderHandles from './ModelNodeHeaderHandles' |
10 | 11 | import ModelColumns from './ModelColumns' |
@@ -82,6 +83,8 @@ export default function ModelNode({ |
82 | 83 | ) |
83 | 84 |
|
84 | 85 | const [isMouseOver, setIsMouseOver] = useState(false) |
| 86 | + const headerRef = useRef<HTMLDivElement>(null) |
| 87 | + const columnsWrapperRef = useRef<HTMLDivElement>(null) |
85 | 88 |
|
86 | 89 | const handleClick = useCallback( |
87 | 90 | (e: React.MouseEvent) => { |
@@ -153,7 +156,7 @@ export default function ModelNode({ |
153 | 156 | onMouseEnter={() => setIsMouseOver(true)} |
154 | 157 | onMouseLeave={() => setIsMouseOver(false)} |
155 | 158 | className={clsx( |
156 | | - 'text-xs font-semibold border-4', |
| 159 | + 'text-xs font-semibold border-4 relative', |
157 | 160 | isMouseOver ? 'z-50' : 'z-1', |
158 | 161 | showColumns ? 'rounded-xl' : 'rounded-2xl', |
159 | 162 | (hasHighlightedNodes ? isHighlightedNode : isActiveNode) || isMainNode |
@@ -181,46 +184,64 @@ export default function ModelNode({ |
181 | 184 | ? 'ring-8 ring-neutral-50' |
182 | 185 | : isSelected && 'ring-8 ring-secondary-50 dark:ring-primary-50', |
183 | 186 | )} |
184 | | - style={{ |
185 | | - maxWidth: isNil(nodeData.width) |
186 | | - ? 'auto' |
187 | | - : `${nodeData.width as number}px`, |
188 | | - }} |
| 187 | + style={{ width: '100%' }} |
189 | 188 | > |
190 | | - <ModelNodeHeaderHandles |
191 | | - id={id} |
192 | | - type={nodeData.type} |
193 | | - label={nodeData.label} |
194 | | - isSelected={isSelected} |
195 | | - isDraggable={true} |
196 | | - className={clsx( |
197 | | - 'bg-theme-lighter', |
198 | | - showColumns ? 'rounded-t-[8px]' : 'rounded-xl', |
199 | | - )} |
200 | | - hasLeft={targetPosition === Position.Left && isNil(lineageCache)} |
201 | | - hasRight={sourcePosition === Position.Right && isNil(lineageCache)} |
202 | | - handleClick={isInteractive ? handleClick : undefined} |
203 | | - handleSelect={ |
204 | | - mainNode === id || |
205 | | - isCTE || |
206 | | - hasHighlightedNodes || |
207 | | - isNotNil(lineageCache) |
208 | | - ? undefined |
209 | | - : handleSelect |
210 | | - } |
211 | | - count={hasHighlightedNodes ? undefined : columns.length} |
| 189 | + <NodeResizeControl |
| 190 | + minWidth={150} |
| 191 | + minHeight={36} |
| 192 | + position="bottom-right" |
| 193 | + style={{ |
| 194 | + background: 'transparent', |
| 195 | + border: 'none', |
| 196 | + width: 10, |
| 197 | + height: 10, |
| 198 | + }} |
| 199 | + onResize={(_, params) => { |
| 200 | + const headerH = headerRef.current?.offsetHeight ?? 0 |
| 201 | + const available = Math.max(0, params.height - headerH) |
| 202 | + if (columnsWrapperRef.current) { |
| 203 | + columnsWrapperRef.current.style.height = `${available}px` |
| 204 | + } |
| 205 | + }} |
212 | 206 | /> |
213 | | - {showColumns && ( |
214 | | - <ModelColumns |
215 | | - className="nowheel rounded-b-lg bg-theme-lighter text-xs" |
216 | | - nodeId={id} |
217 | | - columns={columns} |
218 | | - disabled={shouldDisableColumns} |
219 | | - withHandles={true} |
220 | | - withSource={true} |
221 | | - withDescription={false} |
222 | | - maxHeight="10rem" |
| 207 | + <div ref={headerRef}> |
| 208 | + <ModelNodeHeaderHandles |
| 209 | + id={id} |
| 210 | + type={nodeData.type} |
| 211 | + label={nodeData.label} |
| 212 | + isSelected={isSelected} |
| 213 | + isDraggable={true} |
| 214 | + className={clsx( |
| 215 | + 'bg-theme-lighter', |
| 216 | + showColumns ? 'rounded-t-[8px]' : 'rounded-xl', |
| 217 | + )} |
| 218 | + hasLeft={targetPosition === Position.Left && isNil(lineageCache)} |
| 219 | + hasRight={sourcePosition === Position.Right && isNil(lineageCache)} |
| 220 | + handleClick={isInteractive ? handleClick : undefined} |
| 221 | + handleSelect={ |
| 222 | + mainNode === id || |
| 223 | + isCTE || |
| 224 | + hasHighlightedNodes || |
| 225 | + isNotNil(lineageCache) |
| 226 | + ? undefined |
| 227 | + : handleSelect |
| 228 | + } |
| 229 | + count={hasHighlightedNodes ? undefined : columns.length} |
223 | 230 | /> |
| 231 | + </div> |
| 232 | + {showColumns && ( |
| 233 | + <div ref={columnsWrapperRef} style={{ height: '10rem', overflow: 'hidden' }}> |
| 234 | + <ModelColumns |
| 235 | + className="nowheel rounded-b-lg bg-theme-lighter text-xs h-full" |
| 236 | + nodeId={id} |
| 237 | + columns={columns} |
| 238 | + disabled={shouldDisableColumns} |
| 239 | + withHandles={true} |
| 240 | + withSource={true} |
| 241 | + withDescription={false} |
| 242 | + maxHeight="100%" |
| 243 | + /> |
| 244 | + </div> |
224 | 245 | )} |
225 | 246 | </div> |
226 | 247 | ) |
|
0 commit comments