Skip to content

Commit

Permalink
More grid refactor (#6384)
Browse files Browse the repository at this point in the history
**Problem:**
Replace more dom query based logic from grid strategies, and nuke grid
specific X behavior.

**Commit Details:** (< vv pls delete this section if's not relevant)

- Remove `getGridCellAtPoint` and replace its usage with
`getGridCellUnderMouseFromMetadata`
- Rewrite `getGridCellBoundsFromCanvas` to use metadata and canvas
coordinates.
- Nuke grid specific X behavior and all its helpers: let's just remove
the grid cell positioning props when converting to absolute.

**Manual Tests:**
I hereby swear that:

- [x] I opened a hydrogen project and it loaded
- [x] I could navigate to various routes in Preview mode
  • Loading branch information
gbalint authored Sep 18, 2024
1 parent 8ab360f commit 5b1cd24
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 391 deletions.
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import type { ElementPath } from 'utopia-shared/src/types'
import type { ElementInstanceMetadata } from '../../../../core/shared/element-template'
import type { CanvasVector, WindowPoint, WindowRectangle } from '../../../../core/shared/math-utils'
import type { WindowPoint, WindowRectangle } from '../../../../core/shared/math-utils'
import {
canvasPoint,
isInfinityRectangle,
offsetPoint,
rectContainsPoint,
windowPoint,
windowRectangle,
} from '../../../../core/shared/math-utils'
import { canvasPointToWindowPoint } from '../../dom-lookup'
import * as EP from '../../../../core/shared/element-path'
import {
getGlobalFramesOfGridCells,
Expand All @@ -35,25 +34,25 @@ export function gridCellTargetId(

export function getGridCellUnderMouseFromMetadata(
grid: ElementInstanceMetadata,
canvasPoint: CanvasPoint,
point: CanvasPoint,
): TargetGridCellData | null {
const gridCellGlobalFrames = getGlobalFramesOfGridCells(grid)

if (gridCellGlobalFrames == null) {
return null
}

return getGridCellUnderPoint(gridCellGlobalFrames, canvasPoint)
return getGridCellUnderPoint(gridCellGlobalFrames, point)
}

// TODO: we could optimize this with binary search, but huge grids are rare
function getGridCellUnderPoint(
gridCellGlobalFrames: GridCellGlobalFrames,
canvasPoint: CanvasPoint,
point: CanvasPoint,
): TargetGridCellData | null {
for (let i = 0; i < gridCellGlobalFrames.length; i++) {
for (let j = 0; j < gridCellGlobalFrames[i].length; j++) {
if (rectContainsPoint(gridCellGlobalFrames[i][j], canvasPoint)) {
if (rectContainsPoint(gridCellGlobalFrames[i][j], point)) {
return {
gridCellCoordinates: gridCellCoordinates(i + 1, j + 1),
cellCanvasRectangle: gridCellGlobalFrames[i][j],
Expand All @@ -64,97 +63,34 @@ function getGridCellUnderPoint(
return null
}

function isGridCellTargetId(id: string): boolean {
return id.startsWith(gridCellTargetIdPrefix)
}

export function getGridCellAtPoint(
point: WindowPoint,
duplicating: boolean,
): { id: string; coordinates: GridCellCoordinates; cellWindowRectangle: WindowRectangle } | null {
function maybeRecursivelyFindCellAtPoint(
elements: Element[],
): { element: Element; cellWindowRectangle: WindowRectangle } | null {
// If this used during duplication, the canvas controls will be in the way and we need to traverse the children too.
for (const element of elements) {
if (isGridCellTargetId(element.id)) {
const domRect = element.getBoundingClientRect()
const windowRect = windowRectangle(domRect)
if (rectContainsPoint(windowRect, point)) {
return { element: element, cellWindowRectangle: windowRect }
}
}

if (duplicating) {
const child = maybeRecursivelyFindCellAtPoint(Array.from(element.children))
if (child != null) {
return child
}
}
}

return null
}

const cellUnderMouse = maybeRecursivelyFindCellAtPoint(
document.elementsFromPoint(point.x, point.y),
)
if (cellUnderMouse == null) {
return null
}

const { element, cellWindowRectangle } = cellUnderMouse
const row = element.getAttribute('data-grid-row')
const column = element.getAttribute('data-grid-column')

return {
id: element.id,
cellWindowRectangle: cellWindowRectangle,
coordinates: gridCellCoordinates(
row == null ? 0 : parseInt(row),
column == null ? 0 : parseInt(column),
),
}
}

const GRID_BOUNDS_TOLERANCE = 5 // px

export function getGridCellBoundsFromCanvas(
cell: ElementInstanceMetadata,
canvasScale: number,
canvasOffset: CanvasVector,
grid: ElementInstanceMetadata,
) {
const cellFrame = cell.globalFrame
if (cellFrame == null || isInfinityRectangle(cellFrame)) {
return null
}

const canvasFrameWidth = cellFrame.width * canvasScale
const canvasFrameHeight = cellFrame.height * canvasScale

const cellOriginPoint = offsetPoint(
canvasPointToWindowPoint(cellFrame, canvasScale, canvasOffset),
windowPoint({ x: GRID_BOUNDS_TOLERANCE, y: GRID_BOUNDS_TOLERANCE }),
)
const cellOrigin = getGridCellAtPoint(cellOriginPoint, true)
const cellOrigin = getGridCellUnderMouseFromMetadata(grid, cellFrame)
if (cellOrigin == null) {
return null
}

const cellEndPoint = offsetPoint(
cellOriginPoint,
windowPoint({
x: canvasFrameWidth - GRID_BOUNDS_TOLERANCE,
y: canvasFrameHeight - GRID_BOUNDS_TOLERANCE,
cellFrame,
canvasPoint({
x: cellFrame.width,
y: cellFrame.height,
}),
)
const cellEnd = getGridCellAtPoint(cellEndPoint, true)
const cellEnd = getGridCellUnderMouseFromMetadata(grid, cellEndPoint)
if (cellEnd == null) {
return null
}

const cellOriginCoords = cellOrigin.coordinates
const cellEndCoords = cellEnd.coordinates
const cellOriginCoords = cellOrigin.gridCellCoordinates
const cellEndCoords = cellEnd.gridCellCoordinates

const cellWidth = cellEndCoords.column - cellOriginCoords.column + 1
const cellHeight = cellEndCoords.row - cellOriginCoords.row + 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,7 @@ export function gridRearrangeResizeKeyboardStrategy(
}
const gridTemplate = grid.specialSizeMeasurements.containerGridProperties

const initialCellBounds = getGridCellBoundsFromCanvas(
cell,
canvasState.scale,
canvasState.canvasOffset,
)
const initialCellBounds = getGridCellBoundsFromCanvas(cell, grid)
if (initialCellBounds == null) {
return null
}
Expand Down
188 changes: 11 additions & 177 deletions editor/src/components/editor/shortcuts.spec.browser2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -301,11 +301,6 @@ export var storyboard = (
data-testid='child'
style={{
backgroundColor: '#0162ff',
position: 'absolute',
left: 13,
top: 19,
width: 74,
height: 59,
gridColumn: 3,
gridRow: 3,
}}
Expand All @@ -319,194 +314,33 @@ export var storyboard = (
)

await selectComponentsForTest(editor, [EP.fromString('sb/scene/grid/child')])
await pressKey('x')
await pressKey('x') // convert it to absolute

{
const { position, gridRow, gridColumn, width, height } =
editor.renderedDOM.getByTestId('child').style
expect({ position, gridRow, gridColumn, width, height }).toEqual({
gridColumn: '3',
gridRow: '3',
height: '',
position: '',
width: '',
})
}

await pressKey('x') // convert it back to absolute

{
const { position, gridRow, gridColumn, width, height, top, left } =
editor.renderedDOM.getByTestId('child').style
expect({ position, gridRow, gridColumn, width, height, top, left }).toEqual({
gridColumn: '3',
gridRow: '3',
height: '90.5px',
left: '0px',
gridColumn: '',
gridRow: '',
height: '91px',
position: 'absolute',
top: '0px',
width: '104px',
})
}
})

it('can place element spanning multiple grid cells into the grid and take it out', async () => {
const editor = await renderTestEditorWithCode(
`import { Scene, Storyboard } from 'utopia-api'
export var storyboard = (
<Storyboard data-uid='sb'>
<Scene
id='playground-scene'
commentId='playground-scene'
style={{
width: 620,
height: 548,
position: 'absolute',
left: 212,
top: 103,
}}
data-label='Playground'
data-uid='scene'
>
<div
data-uid='grid'
style={{
backgroundColor: '#aaaaaa33',
width: 527,
height: 461,
display: 'grid',
gridTemplateColumns: '1fr 1fr 1fr 1fr 1fr',
gridTemplateRows: '1fr 1fr 1fr 1fr 1fr',
contain: 'layout',
gridGap: 2,
position: 'absolute',
left: 46,
top: 43,
}}
>
<div
data-uid='child'
data-testid='child'
style={{
position: 'absolute',
left: 29,
top: 34,
width: 237,
height: 121,
gridColumn: 2,
gridRow: 2,
backgroundImage:
'linear-gradient(44deg, #BE3B5E 0%, #309212 100%)',
}}
/>
</div>
</Scene>
</Storyboard>
)
`,
'await-first-dom-report',
)

await selectComponentsForTest(editor, [EP.fromString('sb/scene/grid/child')])
await pressKey('x')

{
const { position, gridRow, gridColumn, width, height } =
editor.renderedDOM.getByTestId('child').style
expect({ position, gridRow, gridColumn, width, height }).toEqual({
gridColumn: '2 / 5',
gridRow: '2 / 4',
height: '',
position: '',
width: '',
})
}

await pressKey('x') // convert it back to absolute
await pressKey('x') // convert it back to flow with size kept (but no gridColumn/Row or other grid specific prop is set yet)

{
const { position, gridRow, gridColumn, width, height, top, left } =
editor.renderedDOM.getByTestId('child').style
expect({ position, gridRow, gridColumn, width, height, top, left }).toEqual({
gridColumn: '2',
gridRow: '2',
height: '183px',
left: '0px',
position: 'absolute',
top: '0px',
width: '315.5px',
})
}
})

it('can place element with no grid positioning props set', async () => {
const editor = await renderTestEditorWithCode(
`import { Scene, Storyboard } from 'utopia-api'
export var storyboard = (
<Storyboard data-uid='sb'>
<Scene
id='playground-scene'
commentId='playground-scene'
style={{
width: 620,
height: 548,
position: 'absolute',
left: 212,
top: 103,
}}
data-label='Playground'
data-uid='scene'
>
<div
data-uid='grid'
style={{
backgroundColor: '#aaaaaa33',
width: 527,
height: 461,
display: 'grid',
gridTemplateColumns: '1fr 1fr 1fr 1fr 1fr',
gridTemplateRows: '1fr 1fr 1fr 1fr 1fr',
contain: 'layout',
gridGap: 2,
position: 'absolute',
left: 46,
top: 43,
}}
>
<div
data-uid='child'
data-testid='child'
style={{
backgroundColor: '#0162ff',
position: 'absolute',
left: 113,
top: 119,
width: 174,
height: 119,
}}
/>
</div>
</Scene>
</Storyboard>
)
`,
'await-first-dom-report',
)

await selectComponentsForTest(editor, [EP.fromString('sb/scene/grid/child')])
await pressKey('x')

{
const { position, gridRow, gridColumn, width, height } =
editor.renderedDOM.getByTestId('child').style
expect({ position, gridRow, gridColumn, width, height }).toEqual({
gridColumn: '2 / 4',
gridRow: '2 / 4',
height: '',
gridColumn: '',
gridRow: '',
height: '91px',
left: '',
position: '',
width: '',
top: '',
width: '104px',
})
}
})
Expand Down
Loading

0 comments on commit 5b1cd24

Please sign in to comment.