Skip to content

Commit 0e30131

Browse files
authored
refactor(grid) Grid Element Direct Lookup Ordered Correctly. (#6667)
- Moved a lot of common types and functions used when obtaining values from elements for grids into `grid-measurements.tsx`. - Added `getGridElementMeasurementHelperData` to retrieve the necessary details for a grid element, along with some supporting functions. - Extracted out a `getGlobalFrame` function for the common lookup used. - Changed some test ID values in two components as they created the same ones. - `PaddingResizeControl` set to the `bottom` priority so that it doesn't end up on top of the track resizing controls. - `GridControl` now gets the cell dimensions directly from the grid element. - `GridElementContainingBlock` gets the grid child dimensions directly. - Added `HelperControlsStateContext` and made the connected change to `StoreName`. - Added `helperControlsStore` to `Editor`. - Added contexts for the helper store to `EditorRoot` and `HotRoot`. - Wrapped the contents of `GridElementContainingBlocks` and `GridMeasurementHelpers` with the new helper store context. - Added calls to update the helper store in the regular and test dispatch flows.
1 parent 1b27ff8 commit 0e30131

17 files changed

+544
-389
lines changed

editor/src/components/canvas/canvas-strategies/strategies/resize-grid-strategy.spec.browser2.tsx

+7-7
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ describe('resize a grid', () => {
144144
await renderResult.dispatch(selectComponents([target], false), true)
145145
await renderResult.getDispatchFollowUpActionsFinished()
146146
const canvasControlsLayer = renderResult.renderedDOM.getByTestId(CanvasControlsContainerID)
147-
const resizeControl = renderResult.renderedDOM.getByTestId(`grid-column-handle-1`)
147+
const resizeControl = renderResult.renderedDOM.getByTestId(`grid-track-size-column-handle-1`)
148148
const resizeControlRect = resizeControl.getBoundingClientRect()
149149
const startPoint = canvasPoint({
150150
x: resizeControlRect.x + resizeControlRect.width / 2,
@@ -259,7 +259,7 @@ export var storyboard = (
259259
await renderResult.dispatch(selectComponents([target], false), true)
260260
await renderResult.getDispatchFollowUpActionsFinished()
261261
const canvasControlsLayer = renderResult.renderedDOM.getByTestId(CanvasControlsContainerID)
262-
const resizeControl = renderResult.renderedDOM.getByTestId(`grid-column-handle-1`)
262+
const resizeControl = renderResult.renderedDOM.getByTestId(`grid-track-size-column-handle-1`)
263263
const resizeControlRect = resizeControl.getBoundingClientRect()
264264
const startPoint = canvasPoint({
265265
x: resizeControlRect.x + resizeControlRect.width / 2,
@@ -320,7 +320,7 @@ export var storyboard = (
320320
await renderResult.dispatch(selectComponents([target], false), true)
321321
await renderResult.getDispatchFollowUpActionsFinished()
322322
const canvasControlsLayer = renderResult.renderedDOM.getByTestId(CanvasControlsContainerID)
323-
const resizeControl = renderResult.renderedDOM.getByTestId(`grid-row-handle-1`)
323+
const resizeControl = renderResult.renderedDOM.getByTestId(`grid-track-size-row-handle-1`)
324324
const resizeControlRect = resizeControl.getBoundingClientRect()
325325
const startPoint = canvasPoint({
326326
x: resizeControlRect.x + resizeControlRect.width / 2,
@@ -435,7 +435,7 @@ export var storyboard = (
435435
await renderResult.dispatch(selectComponents([target], false), true)
436436
await renderResult.getDispatchFollowUpActionsFinished()
437437
const canvasControlsLayer = renderResult.renderedDOM.getByTestId(CanvasControlsContainerID)
438-
const resizeControl = renderResult.renderedDOM.getByTestId(`grid-column-handle-1`)
438+
const resizeControl = renderResult.renderedDOM.getByTestId(`grid-track-size-column-handle-1`)
439439
const resizeControlRect = resizeControl.getBoundingClientRect()
440440
const startPoint = canvasPoint({
441441
x: resizeControlRect.x + resizeControlRect.width / 2,
@@ -555,7 +555,7 @@ export var storyboard = (
555555
await renderResult.dispatch(selectComponents([target], false), true)
556556
await renderResult.getDispatchFollowUpActionsFinished()
557557
const canvasControlsLayer = renderResult.renderedDOM.getByTestId(CanvasControlsContainerID)
558-
const resizeControl = renderResult.renderedDOM.getByTestId(`grid-column-handle-1`)
558+
const resizeControl = renderResult.renderedDOM.getByTestId(`grid-track-size-column-handle-1`)
559559
const resizeControlRect = resizeControl.getBoundingClientRect()
560560
const startPoint = canvasPoint({
561561
x: resizeControlRect.x + resizeControlRect.width / 2,
@@ -674,7 +674,7 @@ export var storyboard = (
674674
await renderResult.dispatch(selectComponents([target], false), true)
675675
await renderResult.getDispatchFollowUpActionsFinished()
676676
const canvasControlsLayer = renderResult.renderedDOM.getByTestId(CanvasControlsContainerID)
677-
const resizeControl = renderResult.renderedDOM.getByTestId(`grid-column-handle-1`)
677+
const resizeControl = renderResult.renderedDOM.getByTestId(`grid-track-size-column-handle-1`)
678678
const resizeControlRect = resizeControl.getBoundingClientRect()
679679
const startPoint = canvasPoint({
680680
x: resizeControlRect.x + resizeControlRect.width / 2,
@@ -792,7 +792,7 @@ export var storyboard = (
792792
await renderResult.dispatch(selectComponents([target], false), true)
793793
await renderResult.getDispatchFollowUpActionsFinished()
794794
const canvasControlsLayer = renderResult.renderedDOM.getByTestId(CanvasControlsContainerID)
795-
const resizeControl = renderResult.renderedDOM.getByTestId(`grid-column-handle-1`)
795+
const resizeControl = renderResult.renderedDOM.getByTestId(`grid-track-size-column-handle-1`)
796796
const resizeControlRect = resizeControl.getBoundingClientRect()
797797
const startPoint = canvasPoint({
798798
x: resizeControlRect.x + resizeControlRect.width / 2,

editor/src/components/canvas/canvas-strategies/strategies/set-padding-strategy.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ export const setPaddingStrategy: CanvasStrategyFactory = (canvasState, interacti
131131
props: { targets: selectedElements },
132132
key: 'padding-resize-control',
133133
show: 'visible-except-when-other-strategy-is-active',
134+
priority: 'bottom',
134135
})
135136

136137
const controlsToRender = optionalMap(

editor/src/components/canvas/controls/grid-controls-for-strategies.tsx

+3-235
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,11 @@
11
/** @jsxRuntime classic */
22
/** @jsx jsx */
3-
import { sides, type Sides } from 'utopia-api/core'
43
import type { ElementPath } from 'utopia-shared/src/types'
5-
import {
6-
isStaticGridRepeat,
7-
parseCSSLength,
8-
printGridAutoOrTemplateBase,
9-
} from '../../inspector/common/css-utils'
4+
import { printGridAutoOrTemplateBase } from '../../inspector/common/css-utils'
105
import { MetadataUtils } from '../../../core/model/element-metadata-utils'
116
import { mapDropNulls } from '../../../core/shared/array-utils'
127
import * as EP from '../../../core/shared/element-path'
13-
import type { BorderWidths } from '../../../core/shared/element-template'
148
import { type GridAutoOrTemplateBase } from '../../../core/shared/element-template'
15-
import type { CanvasRectangle } from '../../../core/shared/math-utils'
169
import { assertNever } from '../../../core/shared/utils'
1710
import { Substores, useEditorState } from '../../editor/store/store-hook'
1811
import type {
@@ -23,7 +16,6 @@ import { controlForStrategyMemoized } from '../canvas-strategies/canvas-strategy
2316
import type { GridResizeEdge } from '../canvas-strategies/interaction-state'
2417
import type { EdgePosition } from '../canvas-types'
2518
import {
26-
CanvasContainerID,
2719
EdgePositionBottom,
2820
EdgePositionLeft,
2921
EdgePositionRight,
@@ -35,14 +27,8 @@ import {
3527
GridRowColumnResizingControlsComponent,
3628
} from './grid-controls'
3729
import { isEdgePositionOnSide } from '../canvas-utils'
38-
import { getFromElement } from '../direct-dom-lookups'
39-
import { applicativeSidesPxTransform, getGridContainerProperties } from '../dom-walker'
40-
import { applicative4Either, defaultEither, isRight, mapEither } from '../../../core/shared/either'
41-
import { domRectToScaledCanvasRectangle, getRoundingFn } from '../../../core/shared/dom-utils'
42-
import Utils from '../../../utils/utils'
4330
import { useMonitorChangesToElements } from '../../../components/editor/store/store-monitor'
4431
import { useKeepReferenceEqualityIfPossible } from '../../../utils/react-performance'
45-
import type { CSSProperties } from 'react'
4632
import {
4733
gridContainerIdentifier,
4834
gridItemIdentifier,
@@ -51,29 +37,13 @@ import {
5137
} from '../../editor/store/editor-state'
5238
import { findOriginalGrid } from '../canvas-strategies/strategies/grid-helpers'
5339
import * as React from 'react'
54-
import { addChangeCallback, removeChangeCallback } from '../observers'
40+
import type { GridData } from './grid-measurements'
41+
import { getGridMeasurementHelperData, useObserversToWatch } from './grid-measurements'
5542

5643
export const GridMeasurementHelperMap = { current: new WeakMap<HTMLElement, string>() }
5744

5845
export const GridCellTestId = (elementPath: ElementPath) => `grid-cell-${EP.toString(elementPath)}`
5946

60-
function getCellsCount(template: GridAutoOrTemplateBase | null): number {
61-
if (template == null) {
62-
return 0
63-
}
64-
65-
switch (template.type) {
66-
case 'DIMENSIONS':
67-
return template.dimensions.reduce((acc, cur) => {
68-
return acc + (isStaticGridRepeat(cur) ? cur.times : 1)
69-
}, 0)
70-
case 'FALLBACK':
71-
return 0
72-
default:
73-
assertNever(template)
74-
}
75-
}
76-
7747
export function getNullableAutoOrTemplateBaseString(
7848
template: GridAutoOrTemplateBase | null,
7949
): string | undefined {
@@ -84,208 +54,6 @@ export function getNullableAutoOrTemplateBaseString(
8454
}
8555
}
8656

87-
export type GridMeasurementHelperData = {
88-
frame: CanvasRectangle
89-
rows: number
90-
columns: number
91-
cells: number
92-
computedStyling: CSSProperties
93-
gridTemplateColumns: GridAutoOrTemplateBase | null
94-
gridTemplateRows: GridAutoOrTemplateBase | null
95-
gridTemplateColumnsFromProps: GridAutoOrTemplateBase | null
96-
gridTemplateRowsFromProps: GridAutoOrTemplateBase | null
97-
border: BorderWidths
98-
gap: number | null
99-
rowGap: number | null
100-
columnGap: number | null
101-
padding: Sides
102-
element: HTMLElement
103-
}
104-
105-
export type ElementOrParent = 'parent' | 'element'
106-
107-
export function getGridMeasurementHelperData(
108-
elementPath: ElementPath,
109-
scale: number,
110-
source: ElementOrParent,
111-
): GridMeasurementHelperData | undefined {
112-
return getFromElement(elementPath, gridMeasurementHelperDataFromElement(scale), source)
113-
}
114-
115-
function getStylingSubset(styling: CSSStyleDeclaration): CSSProperties {
116-
// Fields chosen to not overlap with any others, so as to not make React complain.
117-
return {
118-
gridAutoFlow: styling.gridAutoFlow,
119-
gridAutoColumns: styling.gridAutoColumns,
120-
gridAutoRows: styling.gridAutoRows,
121-
gridTemplateColumns: styling.gridTemplateColumns,
122-
gridTemplateRows: styling.gridTemplateRows,
123-
gridColumn: styling.gridColumn,
124-
gridRow: styling.gridRow,
125-
gap: styling.gap,
126-
rowGap: styling.rowGap,
127-
columnGap: styling.columnGap,
128-
justifyContent: styling.justifyContent,
129-
alignContent: styling.alignContent,
130-
padding: styling.padding,
131-
paddingTop: styling.paddingTop,
132-
paddingLeft: styling.paddingLeft,
133-
paddingBottom: styling.paddingBottom,
134-
paddingRight: styling.paddingRight,
135-
borderTop: styling.borderTopWidth,
136-
borderLeft: styling.borderLeftWidth,
137-
borderBottom: styling.borderBottomWidth,
138-
borderRight: styling.borderRightWidth,
139-
}
140-
}
141-
142-
export function gridMeasurementHelperDataFromElement(
143-
scale: number,
144-
): (element: HTMLElement) => GridMeasurementHelperData | undefined {
145-
return (element) => {
146-
const canvasRootContainer = document.getElementById(CanvasContainerID)
147-
if (canvasRootContainer == null) {
148-
return undefined
149-
}
150-
151-
const computedStyle = getComputedStyle(element)
152-
const boundingRectangle = element.getBoundingClientRect()
153-
154-
const computedStyling: CSSProperties = getStylingSubset(computedStyle)
155-
156-
const containerGridProperties = getGridContainerProperties(computedStyle)
157-
const containerGridPropertiesFromProps = getGridContainerProperties(element.style)
158-
159-
const columns = getCellsCount(containerGridProperties.gridTemplateColumns)
160-
const rows = getCellsCount(containerGridProperties.gridTemplateRows)
161-
const borderTopWidth = parseCSSLength(computedStyle.borderTopWidth)
162-
const borderRightWidth = parseCSSLength(computedStyle.borderRightWidth)
163-
const borderBottomWidth = parseCSSLength(computedStyle.borderBottomWidth)
164-
const borderLeftWidth = parseCSSLength(computedStyle.borderLeftWidth)
165-
const border: BorderWidths = {
166-
top: isRight(borderTopWidth) ? borderTopWidth.value.value : 0,
167-
right: isRight(borderRightWidth) ? borderRightWidth.value.value : 0,
168-
bottom: isRight(borderBottomWidth) ? borderBottomWidth.value.value : 0,
169-
left: isRight(borderLeftWidth) ? borderLeftWidth.value.value : 0,
170-
}
171-
const padding = defaultEither(
172-
sides(undefined, undefined, undefined, undefined),
173-
applicative4Either(
174-
applicativeSidesPxTransform,
175-
parseCSSLength(computedStyle.paddingTop),
176-
parseCSSLength(computedStyle.paddingRight),
177-
parseCSSLength(computedStyle.paddingBottom),
178-
parseCSSLength(computedStyle.paddingLeft),
179-
),
180-
)
181-
const gap = defaultEither(
182-
null,
183-
mapEither((n) => n.value, parseCSSLength(computedStyle.gap)),
184-
)
185-
186-
const rowGap = defaultEither(
187-
null,
188-
mapEither((n) => n.value, parseCSSLength(computedStyle.rowGap)),
189-
)
190-
191-
const columnGap = defaultEither(
192-
null,
193-
mapEither((n) => n.value, parseCSSLength(computedStyle.columnGap)),
194-
)
195-
196-
const elementRect = domRectToScaledCanvasRectangle(
197-
boundingRectangle,
198-
1 / scale,
199-
getRoundingFn('nearest-half'),
200-
)
201-
const parentRect = domRectToScaledCanvasRectangle(
202-
canvasRootContainer.getBoundingClientRect(),
203-
1 / scale,
204-
getRoundingFn('nearest-half'),
205-
)
206-
const frame = Utils.offsetRect(elementRect, Utils.negate(parentRect))
207-
208-
return {
209-
frame: frame,
210-
gridTemplateColumns: containerGridProperties.gridTemplateColumns,
211-
gridTemplateRows: containerGridProperties.gridTemplateRows,
212-
gridTemplateColumnsFromProps: containerGridPropertiesFromProps.gridTemplateColumns,
213-
gridTemplateRowsFromProps: containerGridPropertiesFromProps.gridTemplateRows,
214-
border: border,
215-
padding: padding,
216-
gap: gap,
217-
rowGap: rowGap,
218-
columnGap: columnGap,
219-
rows: rows,
220-
columns: columns,
221-
cells: columns * rows,
222-
computedStyling: computedStyling,
223-
element: element,
224-
}
225-
}
226-
}
227-
228-
function useObserversToWatch(elementPathOrPaths: Array<ElementPath> | ElementPath): number {
229-
// Used to trigger extra renders.
230-
const [counter, setCounter] = React.useState(0)
231-
const bumpCounter = React.useCallback(() => {
232-
setCounter((value) => value + 1)
233-
}, [])
234-
235-
// Need to use the mount count for the callback trigger.
236-
const mountCount = useEditorState(
237-
Substores.canvas,
238-
(store) => store.editor.canvas.mountCount,
239-
'useObserversToWatch mountCount',
240-
)
241-
242-
React.useEffect(() => {
243-
// Add the change callback(s) for the element path or paths.
244-
if (Array.isArray(elementPathOrPaths)) {
245-
for (const elementPath of elementPathOrPaths) {
246-
addChangeCallback(mountCount, elementPath, bumpCounter)
247-
}
248-
} else {
249-
addChangeCallback(mountCount, elementPathOrPaths, bumpCounter)
250-
}
251-
252-
return function cleanup() {
253-
if (Array.isArray(elementPathOrPaths)) {
254-
for (const elementPath of elementPathOrPaths) {
255-
removeChangeCallback(elementPath, bumpCounter)
256-
}
257-
} else {
258-
removeChangeCallback(elementPathOrPaths, bumpCounter)
259-
}
260-
}
261-
}, [mountCount, elementPathOrPaths, bumpCounter])
262-
263-
return counter
264-
}
265-
266-
export function useGridMeasurementHelperData(
267-
elementPath: ElementPath,
268-
source: ElementOrParent,
269-
): GridMeasurementHelperData | undefined {
270-
const scale = useEditorState(
271-
Substores.canvas,
272-
(store) => store.editor.canvas.scale,
273-
'useGridMeasurementHelperData scale',
274-
)
275-
276-
useMonitorChangesToElements([elementPath])
277-
278-
useObserversToWatch(elementPath)
279-
280-
return useKeepReferenceEqualityIfPossible(
281-
getGridMeasurementHelperData(elementPath, scale, source),
282-
)
283-
}
284-
285-
export type GridData = GridMeasurementHelperData & {
286-
identifier: GridIdentifier
287-
}
288-
28957
export function useGridData(gridIdentifiers: GridIdentifier[]): GridData[] {
29058
const scale = useEditorState(
29159
Substores.canvas,

editor/src/components/canvas/controls/grid-controls-helpers.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { CSSProperties } from 'react'
2-
import type { GridMeasurementHelperData } from './grid-controls-for-strategies'
2+
import type { GridMeasurementHelperData } from './grid-measurements'
33

44
export function getGridHelperStyleMatchingTargetGrid(
55
grid: GridMeasurementHelperData,

0 commit comments

Comments
 (0)