Skip to content

Commit

Permalink
Calculate global frame of grid cells (#6371)
Browse files Browse the repository at this point in the history
**Problem:**
Grid strategies use DOM api to find grid cells under the mouse, find
their bounding box, etc.
That is against the strategy contract: strategies should rely on their
input and should not inspect the dom.
This PR is a first step to eliminate these: it introduces a function
which calculates the global frames of grid cells from some fields of
SpecialSizeMeasurements.

Note: we use SpecialSizeMeasurements.containerGridProperties`, and that
has a way more general type than necessary (it uses the same type as the
parsed style prop, so it allows keywords and not just numbers).

**TODO:**
- [ ] refactor the types, and create a `ComputedGridContainerProperties`
which only allows numbers in the dimensions
- [ ] memoize the function
- [ ] refactor grid helpers to rely on metadata and not on the dom api

**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 16, 2024
1 parent 1798bb8 commit efc219c
Show file tree
Hide file tree
Showing 2 changed files with 328 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
import { matchInlineSnapshotBrowser } from '../../../../../test/karma-snapshots'
import { runDOMWalker } from '../../../editor/actions/action-creators'
import { makeTestProjectCodeWithSnippet, renderTestEditorWithCode } from '../../ui-jsx.test-utils'
import { getGlobalFramesOfGridCellsFromMetadata } from './grid-helpers'

describe('Grids', () => {
it('can calculate global frames of grid cells', async () => {
const editor = await renderTestEditorWithCode(
makeTestProjectCodeWithSnippet(`<div
style={{
backgroundColor: '#fff',
position: 'absolute',
left: 127,
top: 84,
width: 519,
height: 443,
display: 'grid',
gridTemplateColumns: '150px min-content 1fr 1fr',
gridTemplateRows: 'min-content 1fr 1fr 1fr 1fr',
gridGap: 10,
padding: 10,
}}
data-uid={'grid'}
>
<div
style={{
backgroundColor: '#09f',
width: '100%',
height: '100%',
gridColumn: 1,
gridRow: 1,
borderRadius: 10,
}}
data-uid={'child'}
/>
<div
style={{
backgroundColor: '#9f0',
width: 80,
height: 50,
gridColumn: 2,
gridRow: 1,
borderTopLeftRadius: 10,
borderTopRightRadius: 10,
borderBottomRightRadius: 10,
borderBottomLeftRadius: 10,
}}
/>
<div
style={{
backgroundColor: '#f09',
width: '100%',
height: '100%',
gridColumn: 1,
gridRowStart: 2,
gridRowEnd: 6,
borderTopLeftRadius: 10,
borderTopRightRadius: 10,
borderBottomRightRadius: 10,
borderBottomLeftRadius: 10,
}}
/>
<div
style={{
backgroundColor: '#f90',
width: '100%',
height: '100%',
gridColumn: 2,
gridRow: 3,
borderRadius: 10,
}}
/>
<div
style={{
backgroundColor: '#f90',
width: '100%',
height: '100%',
gridColumn: 2,
gridRow: 3,
borderRadius: 10,
}}
/>
<div
style={{
backgroundColor: '#90f',
width: '100%',
height: '100%',
gridColumn: 2,
gridRow: 4,
borderRadius: 10,
}}
/>
<div
style={{
backgroundColor: '#0f9',
width: 39,
height: 39,
alignSelf: 'center',
justifySelf: 'center',
gridColumn: 2,
gridRow: 5,
borderRadius: 10,
}}
/>
</div>`),
'await-first-dom-report',
)

await editor.dispatch([runDOMWalker()], true)

// non-grids don't have cell measurements:
expect(
getGlobalFramesOfGridCellsFromMetadata(
editor.getEditorState().editor.jsxMetadata[
'utopia-storyboard-uid/scene-aaa/app-entity:grid/child'
],
),
).toBeNull()

// grids have cell measurements:
matchInlineSnapshotBrowser(
getGlobalFramesOfGridCellsFromMetadata(
editor.getEditorState().editor.jsxMetadata[
'utopia-storyboard-uid/scene-aaa/app-entity:grid'
],
),
`Array [
Array [
Object {
\"height\": 50,
\"width\": 150,
\"x\": 10,
\"y\": 10,
},
Object {
\"height\": 50,
\"width\": 80,
\"x\": 170,
\"y\": 10,
},
Object {
\"height\": 50,
\"width\": 119.5,
\"x\": 260,
\"y\": 10,
},
Object {
\"height\": 50,
\"width\": 119.5,
\"x\": 389.5,
\"y\": 10,
},
],
Array [
Object {
\"height\": 83.25,
\"width\": 150,
\"x\": 10,
\"y\": 70,
},
Object {
\"height\": 83.25,
\"width\": 80,
\"x\": 170,
\"y\": 70,
},
Object {
\"height\": 83.25,
\"width\": 119.5,
\"x\": 260,
\"y\": 70,
},
Object {
\"height\": 83.25,
\"width\": 119.5,
\"x\": 389.5,
\"y\": 70,
},
],
Array [
Object {
\"height\": 83.25,
\"width\": 150,
\"x\": 10,
\"y\": 163.25,
},
Object {
\"height\": 83.25,
\"width\": 80,
\"x\": 170,
\"y\": 163.25,
},
Object {
\"height\": 83.25,
\"width\": 119.5,
\"x\": 260,
\"y\": 163.25,
},
Object {
\"height\": 83.25,
\"width\": 119.5,
\"x\": 389.5,
\"y\": 163.25,
},
],
Array [
Object {
\"height\": 83.25,
\"width\": 150,
\"x\": 10,
\"y\": 256.5,
},
Object {
\"height\": 83.25,
\"width\": 80,
\"x\": 170,
\"y\": 256.5,
},
Object {
\"height\": 83.25,
\"width\": 119.5,
\"x\": 260,
\"y\": 256.5,
},
Object {
\"height\": 83.25,
\"width\": 119.5,
\"x\": 389.5,
\"y\": 256.5,
},
],
Array [
Object {
\"height\": 83.25,
\"width\": 150,
\"x\": 10,
\"y\": 349.75,
},
Object {
\"height\": 83.25,
\"width\": 80,
\"x\": 170,
\"y\": 349.75,
},
Object {
\"height\": 83.25,
\"width\": 119.5,
\"x\": 260,
\"y\": 349.75,
},
Object {
\"height\": 83.25,
\"width\": 119.5,
\"x\": 389.5,
\"y\": 349.75,
},
],
]`,
)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as EP from '../../../../core/shared/element-path'
import type {
ElementInstanceMetadataMap,
GridPositionValue,
GridTemplate,
} from '../../../../core/shared/element-template'
import {
gridPositionValue,
Expand All @@ -12,9 +13,14 @@ import {
type GridElementProperties,
type GridPosition,
} from '../../../../core/shared/element-template'
import type { CanvasVector, WindowRectangle } from '../../../../core/shared/math-utils'
import type {
CanvasRectangle,
CanvasVector,
WindowRectangle,
} from '../../../../core/shared/math-utils'
import {
canvasPoint,
canvasRectangle,
isInfinityRectangle,
offsetPoint,
scaleVector,
Expand All @@ -40,6 +46,7 @@ import {
getGridCellUnderMouseRecursive,
gridCellCoordinates,
} from './grid-cell-bounds'
import type { Sides } from 'utopia-api/core'

export function runGridRearrangeMove(
targetElement: ElementPath,
Expand Down Expand Up @@ -604,3 +611,62 @@ function getGridPositionIndex(props: {
}): number {
return (props.row - 1) * props.gridTemplateColumns + props.column - 1
}

export function getGlobalFramesOfGridCellsFromMetadata(
metadata: ElementInstanceMetadata,
): Array<Array<CanvasRectangle>> | null {
return getGlobalFramesOfGridCells(metadata.specialSizeMeasurements)
}

export function getGlobalFramesOfGridCells({
containerGridProperties,
rowGap,
columnGap,
padding,
}: {
containerGridProperties: GridContainerProperties
rowGap: number | null
columnGap: number | null
padding: Sides
}): Array<Array<CanvasRectangle>> | null {
const columnWidths = gridTemplateToNumbers(containerGridProperties.gridTemplateColumns)

const rowHeights = gridTemplateToNumbers(containerGridProperties.gridTemplateRows)

if (columnWidths == null || rowHeights == null) {
return null
}

const cellRects: Array<Array<CanvasRectangle>> = []
let yOffset = padding.top ?? 0
rowHeights.forEach((height) => {
let xOffset = padding.left ?? 0
const rowRects: CanvasRectangle[] = []
columnWidths.forEach((width) => {
const rect = canvasRectangle({ x: xOffset, y: yOffset, width: width, height: height })
rowRects.push(rect)
xOffset += width + (columnGap ?? 0)
})
cellRects.push(rowRects)
yOffset += height + (rowGap ?? 0)
})

return cellRects
}

function gridTemplateToNumbers(gridTemplate: GridTemplate | null): Array<number> | null {
if (gridTemplate?.type !== 'DIMENSIONS') {
return null
}

const result: Array<number> = []

for (const dimension of gridTemplate.dimensions) {
if (dimension.type !== 'NUMBER') {
return null
}
result.push(dimension.value.value)
}

return result
}

0 comments on commit efc219c

Please sign in to comment.