-
-
Notifications
You must be signed in to change notification settings - Fork 329
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make DataTable vertically resizeable #700
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,9 +44,13 @@ import { OverlayGrid, OverlayGridHandle } from './OverlayGrid'; | |
import NodeHud from './NodeHud'; | ||
import NodeDropArea from './NodeDropArea'; | ||
|
||
const RESIZE_SNAP_UNITS = 4; // px | ||
const HORIZONTAL_RESIZE_SNAP_UNITS = 4; // px | ||
const SNAP_TO_GRID_COLUMN_MARGIN = 10; // px | ||
|
||
const VERTICAL_RESIZE_SNAP_UNITS = 2; // px | ||
|
||
const MIN_RESIZABLE_ELEMENT_HEIGHT = 100; // px | ||
|
||
const classes = { | ||
view: 'Toolpad_View', | ||
}; | ||
|
@@ -65,7 +69,8 @@ const overlayClasses = { | |
nodeHud: 'Toolpad_NodeHud', | ||
container: 'Toolpad_Container', | ||
componentDragging: 'Toolpad_ComponentDragging', | ||
resize: 'Toolpad_Resize', | ||
resizeHorizontal: 'Toolpad_ResizeHorizontal', | ||
resizeVertical: 'Toolpad_ResizeVertical', | ||
hudOverlay: 'Toolpad_HudOverlay', | ||
}; | ||
|
||
|
@@ -79,9 +84,12 @@ const OverlayRoot = styled('div')({ | |
[`&.${overlayClasses.componentDragging}`]: { | ||
cursor: 'copy', | ||
}, | ||
[`&.${overlayClasses.resize}`]: { | ||
[`&.${overlayClasses.resizeHorizontal}`]: { | ||
cursor: 'ew-resize', | ||
}, | ||
[`&.${overlayClasses.resizeVertical}`]: { | ||
cursor: 'ns-resize', | ||
}, | ||
[`.${overlayClasses.hudOverlay}`]: { | ||
position: 'absolute', | ||
inset: '0 0 0 0', | ||
|
@@ -1318,43 +1326,59 @@ export default function RenderPanel({ className }: RenderPanelProps) { | |
const cursorPos = canvasHostRef.current?.getViewCoordinates(event.clientX, event.clientY); | ||
|
||
if (draggedNodeRect && parentRect && resizePreviewElement && cursorPos) { | ||
let snappedToGridCursorPosX = | ||
Math.round(cursorPos.x / RESIZE_SNAP_UNITS) * RESIZE_SNAP_UNITS; | ||
if (draggedEdge === RECTANGLE_EDGE_LEFT || draggedEdge === RECTANGLE_EDGE_RIGHT) { | ||
let snappedToGridCursorRelativePosX = | ||
Math.ceil((cursorPos.x - draggedNodeRect.x) / HORIZONTAL_RESIZE_SNAP_UNITS) * | ||
HORIZONTAL_RESIZE_SNAP_UNITS; | ||
|
||
const activeSnapGridColumnEdges = | ||
draggedEdge === RECTANGLE_EDGE_LEFT | ||
? overlayGridRef.current.getLeftColumnEdges() | ||
: overlayGridRef.current.getRightColumnEdges(); | ||
|
||
for (const gridColumnEdge of activeSnapGridColumnEdges) { | ||
if (Math.abs(gridColumnEdge - cursorPos.x) <= SNAP_TO_GRID_COLUMN_MARGIN) { | ||
snappedToGridCursorRelativePosX = gridColumnEdge - draggedNodeRect.x; | ||
} | ||
} | ||
|
||
const minGridColumnWidth = overlayGridRef.current.getMinColumnWidth(); | ||
|
||
const activeSnapGridColumnEdges = | ||
draggedEdge === RECTANGLE_EDGE_LEFT | ||
? overlayGridRef.current.getLeftColumnEdges() | ||
: overlayGridRef.current.getRightColumnEdges(); | ||
if ( | ||
draggedEdge === RECTANGLE_EDGE_LEFT && | ||
cursorPos.x > parentRect.x + minGridColumnWidth && | ||
cursorPos.x < draggedNodeRect.x + draggedNodeRect.width - minGridColumnWidth | ||
) { | ||
const updatedTransformScale = | ||
1 - snappedToGridCursorRelativePosX / draggedNodeRect.width; | ||
|
||
for (const gridColumnEdge of activeSnapGridColumnEdges) { | ||
if (Math.abs(gridColumnEdge - cursorPos.x) <= SNAP_TO_GRID_COLUMN_MARGIN) { | ||
snappedToGridCursorPosX = gridColumnEdge; | ||
resizePreviewElement.style.transformOrigin = '100% 50%'; | ||
resizePreviewElement.style.transform = `scaleX(${updatedTransformScale})`; | ||
} | ||
if ( | ||
draggedEdge === RECTANGLE_EDGE_RIGHT && | ||
cursorPos.x > draggedNodeRect.x + minGridColumnWidth && | ||
cursorPos.x < parentRect.x + parentRect.width - minGridColumnWidth | ||
) { | ||
const updatedTransformScale = snappedToGridCursorRelativePosX / draggedNodeRect.width; | ||
|
||
resizePreviewElement.style.transformOrigin = '0 50%'; | ||
resizePreviewElement.style.transform = `scaleX(${updatedTransformScale})`; | ||
} | ||
} | ||
|
||
const minGridColumnWidth = overlayGridRef.current.getMinColumnWidth(); | ||
|
||
if ( | ||
draggedEdge === RECTANGLE_EDGE_LEFT && | ||
cursorPos.x > parentRect.x + minGridColumnWidth && | ||
cursorPos.x < draggedNodeRect.x + draggedNodeRect.width - minGridColumnWidth | ||
draggedEdge === RECTANGLE_EDGE_BOTTOM && | ||
cursorPos.y > draggedNodeRect.y + MIN_RESIZABLE_ELEMENT_HEIGHT | ||
) { | ||
const updatedTransformScale = | ||
1 + (draggedNodeRect.x - snappedToGridCursorPosX) / draggedNodeRect.width; | ||
const snappedToGridCursorRelativePosY = | ||
Math.ceil((cursorPos.y - draggedNodeRect.y) / VERTICAL_RESIZE_SNAP_UNITS) * | ||
VERTICAL_RESIZE_SNAP_UNITS; | ||
|
||
resizePreviewElement.style.transformOrigin = '100% 50%'; | ||
resizePreviewElement.style.transform = `scale(${updatedTransformScale}, 1)`; | ||
} | ||
if ( | ||
draggedEdge === RECTANGLE_EDGE_RIGHT && | ||
cursorPos.x > draggedNodeRect.x + minGridColumnWidth && | ||
cursorPos.x < parentRect.x + parentRect.width - minGridColumnWidth | ||
) { | ||
const updatedTransformScale = | ||
(snappedToGridCursorPosX - draggedNodeRect.x) / draggedNodeRect.width; | ||
const updatedTransformScale = snappedToGridCursorRelativePosY / draggedNodeRect.height; | ||
|
||
resizePreviewElement.style.transformOrigin = '0 50%'; | ||
resizePreviewElement.style.transform = `scale(${updatedTransformScale}, 1)`; | ||
resizePreviewElement.style.transformOrigin = '50% 0'; | ||
resizePreviewElement.style.transform = `scaleY(${updatedTransformScale})`; | ||
} | ||
} | ||
}, | ||
|
@@ -1384,74 +1408,89 @@ export default function RenderPanel({ className }: RenderPanelProps) { | |
|
||
const parent = appDom.getParent(dom, draggedNode); | ||
|
||
const parentChildren = parent ? appDom.getChildNodes(dom, parent).children : []; | ||
const totalLayoutColumnSizes = parentChildren.reduce( | ||
(acc, child) => acc + (nodesInfo[child.id]?.rect?.width || 0), | ||
0, | ||
); | ||
|
||
const resizePreviewRect = resizePreviewElement?.getBoundingClientRect(); | ||
|
||
if (draggedNodeRect && resizePreviewRect) { | ||
const normalizeColumnSize = (size: number) => | ||
Math.max(0, size * parentChildren.length) / totalLayoutColumnSizes; | ||
if (draggedEdge === RECTANGLE_EDGE_LEFT || draggedEdge === RECTANGLE_EDGE_RIGHT) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd suggest to brainstorm how this code could be made flatter, it's cyclomatic complexity seems to keep growing. In addition we should also consider how to split this file into smaller chuncks as +1.5k LoC doesn't seem to be very scalable IMO There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With the risk of turning this in a religious debate, I don't like using LoC as a metric for maintainability. I've seen people trash projects by splitting code that semantically belongs together purely for the sake of reducing LoC per file. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not using LoC as a metric, but at certain number I'd say it signals that it could be split and that it might be doing too many things. Without spending too much time in the code my initial impressions that the code inside this component should be mostly responsible for rendering panel. This most recent addition takes care of resizing - so that might be considered as another responsibility, then skimming through the code I see mentions of:
I'm fine with you keeping the structure as it is if you don't find it more difficult to navigate and deal with the code. It would be just interesting to understand what would be a good examples of:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hey Vytautas, I understand your points and both points of view on the subject. there is logic here that could be separated in different files according to its different responsibilities, but we would have to decide on which way to go about that:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For now please disregard my suggestion and feel free to proceed with merging when you feel comfortable. I think we could have a separate discussion where we discuss these sort of things, until we have a clear path and agreement we should not block development :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in regards to the many nested There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
ok, i have it noted to try to improve and make this component more concise and readable whenever i have the chance - but if we can set some common practices and standards that might be helpful! |
||
const parentChildren = parent ? appDom.getChildNodes(dom, parent).children : []; | ||
const totalLayoutColumnSizes = parentChildren.reduce( | ||
(acc, child) => acc + (nodesInfo[child.id]?.rect?.width || 0), | ||
0, | ||
); | ||
|
||
if (draggedEdge === RECTANGLE_EDGE_LEFT) { | ||
const previousSibling = appDom.getSiblingBeforeNode(dom, draggedNode, 'children'); | ||
const normalizeColumnSize = (size: number) => | ||
Math.max(0, size * parentChildren.length) / totalLayoutColumnSizes; | ||
|
||
if (previousSibling) { | ||
const previousSiblingInfo = nodesInfo[previousSibling.id]; | ||
const previousSiblingRect = previousSiblingInfo?.rect; | ||
if (draggedEdge === RECTANGLE_EDGE_LEFT) { | ||
const previousSibling = appDom.getSiblingBeforeNode(dom, draggedNode, 'children'); | ||
|
||
if (previousSiblingRect) { | ||
const updatedDraggedNodeColumnSize = normalizeColumnSize(resizePreviewRect.width); | ||
const updatedPreviousSiblingColumnSize = normalizeColumnSize( | ||
previousSiblingRect.width - (resizePreviewRect.width - draggedNodeRect.width), | ||
); | ||
if (previousSibling) { | ||
const previousSiblingInfo = nodesInfo[previousSibling.id]; | ||
const previousSiblingRect = previousSiblingInfo?.rect; | ||
|
||
domApi.setNodeNamespacedProp( | ||
draggedNode, | ||
'layout', | ||
'columnSize', | ||
appDom.createConst(updatedDraggedNodeColumnSize), | ||
); | ||
domApi.setNodeNamespacedProp( | ||
previousSibling, | ||
'layout', | ||
'columnSize', | ||
appDom.createConst(updatedPreviousSiblingColumnSize), | ||
); | ||
if (previousSiblingRect) { | ||
const updatedDraggedNodeColumnSize = normalizeColumnSize(resizePreviewRect.width); | ||
const updatedPreviousSiblingColumnSize = normalizeColumnSize( | ||
previousSiblingRect.width - (resizePreviewRect.width - draggedNodeRect.width), | ||
); | ||
|
||
domApi.setNodeNamespacedProp( | ||
draggedNode, | ||
'layout', | ||
'columnSize', | ||
appDom.createConst(updatedDraggedNodeColumnSize), | ||
); | ||
domApi.setNodeNamespacedProp( | ||
previousSibling, | ||
'layout', | ||
'columnSize', | ||
appDom.createConst(updatedPreviousSiblingColumnSize), | ||
); | ||
} | ||
} | ||
} | ||
} | ||
if (draggedEdge === RECTANGLE_EDGE_RIGHT) { | ||
const nextSibling = appDom.getSiblingAfterNode(dom, draggedNode, 'children'); | ||
if (draggedEdge === RECTANGLE_EDGE_RIGHT) { | ||
const nextSibling = appDom.getSiblingAfterNode(dom, draggedNode, 'children'); | ||
|
||
if (nextSibling) { | ||
const nextSiblingInfo = nodesInfo[nextSibling.id]; | ||
const nextSiblingRect = nextSiblingInfo?.rect; | ||
if (nextSibling) { | ||
const nextSiblingInfo = nodesInfo[nextSibling.id]; | ||
const nextSiblingRect = nextSiblingInfo?.rect; | ||
|
||
if (nextSiblingRect) { | ||
const updatedDraggedNodeColumnSize = normalizeColumnSize(resizePreviewRect.width); | ||
const updatedNextSiblingColumnSize = normalizeColumnSize( | ||
nextSiblingRect.width - (resizePreviewRect.width - draggedNodeRect.width), | ||
); | ||
if (nextSiblingRect) { | ||
const updatedDraggedNodeColumnSize = normalizeColumnSize(resizePreviewRect.width); | ||
const updatedNextSiblingColumnSize = normalizeColumnSize( | ||
nextSiblingRect.width - (resizePreviewRect.width - draggedNodeRect.width), | ||
); | ||
|
||
domApi.setNodeNamespacedProp( | ||
draggedNode, | ||
'layout', | ||
'columnSize', | ||
appDom.createConst(updatedDraggedNodeColumnSize), | ||
); | ||
domApi.setNodeNamespacedProp( | ||
nextSibling, | ||
'layout', | ||
'columnSize', | ||
appDom.createConst(updatedNextSiblingColumnSize), | ||
); | ||
domApi.setNodeNamespacedProp( | ||
draggedNode, | ||
'layout', | ||
'columnSize', | ||
appDom.createConst(updatedDraggedNodeColumnSize), | ||
); | ||
domApi.setNodeNamespacedProp( | ||
nextSibling, | ||
'layout', | ||
'columnSize', | ||
appDom.createConst(updatedNextSiblingColumnSize), | ||
); | ||
} | ||
} | ||
} | ||
} | ||
|
||
if (draggedEdge === RECTANGLE_EDGE_BOTTOM) { | ||
const resizableHeightProp = draggedNodeInfo?.componentConfig?.resizableHeightProp; | ||
|
||
if (resizableHeightProp) { | ||
domApi.setNodeNamespacedProp( | ||
draggedNode, | ||
'props', | ||
resizableHeightProp, | ||
appDom.createConst(resizePreviewRect.height), | ||
); | ||
} | ||
} | ||
} | ||
|
||
api.dragEnd(); | ||
|
@@ -1473,7 +1512,10 @@ export default function RenderPanel({ className }: RenderPanelProps) { | |
<OverlayRoot | ||
className={clsx({ | ||
[overlayClasses.componentDragging]: isDraggingOver, | ||
[overlayClasses.resize]: draggedEdge, | ||
[overlayClasses.resizeHorizontal]: | ||
draggedEdge === RECTANGLE_EDGE_LEFT || draggedEdge === RECTANGLE_EDGE_RIGHT, | ||
[overlayClasses.resizeVertical]: | ||
draggedEdge === RECTANGLE_EDGE_TOP || draggedEdge === RECTANGLE_EDGE_BOTTOM, | ||
})} | ||
// Need this to be able to capture key events | ||
tabIndex={0} | ||
|
@@ -1527,6 +1569,8 @@ export default function RenderPanel({ className }: RenderPanelProps) { | |
? parentSlotChildNodes[parentSlotChildNodes.length - 1].id === node.id | ||
: false; | ||
|
||
const isVerticallyResizable = Boolean(nodeInfo?.componentConfig?.resizableHeightProp); | ||
|
||
const nodeRect = nodeInfo?.rect || null; | ||
const hasNodeOverlay = isPageNode || appDom.isElement(node); | ||
|
||
|
@@ -1543,15 +1587,18 @@ export default function RenderPanel({ className }: RenderPanelProps) { | |
selected={selectedNode?.id === node.id} | ||
allowInteraction={nodesWithInteraction.has(node.id) && !draggedEdge} | ||
onNodeDragStart={handleNodeDragStart(node)} | ||
draggableEdges={ | ||
isPageRowChild | ||
draggableEdges={[ | ||
...(isPageRowChild | ||
? [ | ||
...(isFirstChild ? [] : [RECTANGLE_EDGE_LEFT as RectangleEdge]), | ||
...(isLastChild ? [] : [RECTANGLE_EDGE_RIGHT as RectangleEdge]), | ||
] | ||
: [] | ||
: []), | ||
...(isVerticallyResizable ? [RECTANGLE_EDGE_BOTTOM as RectangleEdge] : []), | ||
]} | ||
onEdgeDragStart={ | ||
isPageRowChild || isVerticallyResizable ? handleEdgeDragStart : undefined | ||
} | ||
onEdgeDragStart={isPageRowChild ? handleEdgeDragStart : undefined} | ||
onDelete={handleDelete(node.id)} | ||
isResizing={Boolean(draggedEdge) && node.id === draggedNodeId} | ||
resizePreviewElementRef={resizePreviewElementRef} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that there is a lot of this "resizing" logic going on in this component. Could we maybe abstract it away to some
ResizeablePanel
component? 🤔There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in addition to the above, i haven't considered this approach so will see if there's a way to move this logic into a separate React component. thanks for the suggestion!