Skip to content
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

Grid Element Direct Lookup Ordered Correctly. #6667

Merged
merged 5 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ describe('resize a grid', () => {
await renderResult.dispatch(selectComponents([target], false), true)
await renderResult.getDispatchFollowUpActionsFinished()
const canvasControlsLayer = renderResult.renderedDOM.getByTestId(CanvasControlsContainerID)
const resizeControl = renderResult.renderedDOM.getByTestId(`grid-column-handle-1`)
const resizeControl = renderResult.renderedDOM.getByTestId(`grid-track-size-column-handle-1`)
const resizeControlRect = resizeControl.getBoundingClientRect()
const startPoint = canvasPoint({
x: resizeControlRect.x + resizeControlRect.width / 2,
Expand Down Expand Up @@ -259,7 +259,7 @@ export var storyboard = (
await renderResult.dispatch(selectComponents([target], false), true)
await renderResult.getDispatchFollowUpActionsFinished()
const canvasControlsLayer = renderResult.renderedDOM.getByTestId(CanvasControlsContainerID)
const resizeControl = renderResult.renderedDOM.getByTestId(`grid-column-handle-1`)
const resizeControl = renderResult.renderedDOM.getByTestId(`grid-track-size-column-handle-1`)
const resizeControlRect = resizeControl.getBoundingClientRect()
const startPoint = canvasPoint({
x: resizeControlRect.x + resizeControlRect.width / 2,
Expand Down Expand Up @@ -320,7 +320,7 @@ export var storyboard = (
await renderResult.dispatch(selectComponents([target], false), true)
await renderResult.getDispatchFollowUpActionsFinished()
const canvasControlsLayer = renderResult.renderedDOM.getByTestId(CanvasControlsContainerID)
const resizeControl = renderResult.renderedDOM.getByTestId(`grid-row-handle-1`)
const resizeControl = renderResult.renderedDOM.getByTestId(`grid-track-size-row-handle-1`)
const resizeControlRect = resizeControl.getBoundingClientRect()
const startPoint = canvasPoint({
x: resizeControlRect.x + resizeControlRect.width / 2,
Expand Down Expand Up @@ -435,7 +435,7 @@ export var storyboard = (
await renderResult.dispatch(selectComponents([target], false), true)
await renderResult.getDispatchFollowUpActionsFinished()
const canvasControlsLayer = renderResult.renderedDOM.getByTestId(CanvasControlsContainerID)
const resizeControl = renderResult.renderedDOM.getByTestId(`grid-column-handle-1`)
const resizeControl = renderResult.renderedDOM.getByTestId(`grid-track-size-column-handle-1`)
const resizeControlRect = resizeControl.getBoundingClientRect()
const startPoint = canvasPoint({
x: resizeControlRect.x + resizeControlRect.width / 2,
Expand Down Expand Up @@ -555,7 +555,7 @@ export var storyboard = (
await renderResult.dispatch(selectComponents([target], false), true)
await renderResult.getDispatchFollowUpActionsFinished()
const canvasControlsLayer = renderResult.renderedDOM.getByTestId(CanvasControlsContainerID)
const resizeControl = renderResult.renderedDOM.getByTestId(`grid-column-handle-1`)
const resizeControl = renderResult.renderedDOM.getByTestId(`grid-track-size-column-handle-1`)
const resizeControlRect = resizeControl.getBoundingClientRect()
const startPoint = canvasPoint({
x: resizeControlRect.x + resizeControlRect.width / 2,
Expand Down Expand Up @@ -674,7 +674,7 @@ export var storyboard = (
await renderResult.dispatch(selectComponents([target], false), true)
await renderResult.getDispatchFollowUpActionsFinished()
const canvasControlsLayer = renderResult.renderedDOM.getByTestId(CanvasControlsContainerID)
const resizeControl = renderResult.renderedDOM.getByTestId(`grid-column-handle-1`)
const resizeControl = renderResult.renderedDOM.getByTestId(`grid-track-size-column-handle-1`)
const resizeControlRect = resizeControl.getBoundingClientRect()
const startPoint = canvasPoint({
x: resizeControlRect.x + resizeControlRect.width / 2,
Expand Down Expand Up @@ -792,7 +792,7 @@ export var storyboard = (
await renderResult.dispatch(selectComponents([target], false), true)
await renderResult.getDispatchFollowUpActionsFinished()
const canvasControlsLayer = renderResult.renderedDOM.getByTestId(CanvasControlsContainerID)
const resizeControl = renderResult.renderedDOM.getByTestId(`grid-column-handle-1`)
const resizeControl = renderResult.renderedDOM.getByTestId(`grid-track-size-column-handle-1`)
const resizeControlRect = resizeControl.getBoundingClientRect()
const startPoint = canvasPoint({
x: resizeControlRect.x + resizeControlRect.width / 2,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ export const setPaddingStrategy: CanvasStrategyFactory = (canvasState, interacti
props: { targets: selectedElements },
key: 'padding-resize-control',
show: 'visible-except-when-other-strategy-is-active',
priority: 'bottom',
})

const controlsToRender = optionalMap(
Expand Down
238 changes: 3 additions & 235 deletions editor/src/components/canvas/controls/grid-controls-for-strategies.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
/** @jsxRuntime classic */
/** @jsx jsx */
import { sides, type Sides } from 'utopia-api/core'
import type { ElementPath } from 'utopia-shared/src/types'
import {
isStaticGridRepeat,
parseCSSLength,
printGridAutoOrTemplateBase,
} from '../../inspector/common/css-utils'
import { printGridAutoOrTemplateBase } from '../../inspector/common/css-utils'
import { MetadataUtils } from '../../../core/model/element-metadata-utils'
import { mapDropNulls } from '../../../core/shared/array-utils'
import * as EP from '../../../core/shared/element-path'
import type { BorderWidths } from '../../../core/shared/element-template'
import { type GridAutoOrTemplateBase } from '../../../core/shared/element-template'
import type { CanvasRectangle } from '../../../core/shared/math-utils'
import { assertNever } from '../../../core/shared/utils'
import { Substores, useEditorState } from '../../editor/store/store-hook'
import type {
Expand All @@ -23,7 +16,6 @@ import { controlForStrategyMemoized } from '../canvas-strategies/canvas-strategy
import type { GridResizeEdge } from '../canvas-strategies/interaction-state'
import type { EdgePosition } from '../canvas-types'
import {
CanvasContainerID,
EdgePositionBottom,
EdgePositionLeft,
EdgePositionRight,
Expand All @@ -35,14 +27,8 @@ import {
GridRowColumnResizingControlsComponent,
} from './grid-controls'
import { isEdgePositionOnSide } from '../canvas-utils'
import { getFromElement } from '../direct-dom-lookups'
import { applicativeSidesPxTransform, getGridContainerProperties } from '../dom-walker'
import { applicative4Either, defaultEither, isRight, mapEither } from '../../../core/shared/either'
import { domRectToScaledCanvasRectangle, getRoundingFn } from '../../../core/shared/dom-utils'
import Utils from '../../../utils/utils'
import { useMonitorChangesToElements } from '../../../components/editor/store/store-monitor'
import { useKeepReferenceEqualityIfPossible } from '../../../utils/react-performance'
import type { CSSProperties } from 'react'
import {
gridContainerIdentifier,
gridItemIdentifier,
Expand All @@ -51,29 +37,13 @@ import {
} from '../../editor/store/editor-state'
import { findOriginalGrid } from '../canvas-strategies/strategies/grid-helpers'
import * as React from 'react'
import { addChangeCallback, removeChangeCallback } from '../observers'
import type { GridData } from './grid-measurements'
import { getGridMeasurementHelperData, useObserversToWatch } from './grid-measurements'

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

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

function getCellsCount(template: GridAutoOrTemplateBase | null): number {
if (template == null) {
return 0
}

switch (template.type) {
case 'DIMENSIONS':
return template.dimensions.reduce((acc, cur) => {
return acc + (isStaticGridRepeat(cur) ? cur.times : 1)
}, 0)
case 'FALLBACK':
return 0
default:
assertNever(template)
}
}

export function getNullableAutoOrTemplateBaseString(
template: GridAutoOrTemplateBase | null,
): string | undefined {
Expand All @@ -84,208 +54,6 @@ export function getNullableAutoOrTemplateBaseString(
}
}

export type GridMeasurementHelperData = {
frame: CanvasRectangle
rows: number
columns: number
cells: number
computedStyling: CSSProperties
gridTemplateColumns: GridAutoOrTemplateBase | null
gridTemplateRows: GridAutoOrTemplateBase | null
gridTemplateColumnsFromProps: GridAutoOrTemplateBase | null
gridTemplateRowsFromProps: GridAutoOrTemplateBase | null
border: BorderWidths
gap: number | null
rowGap: number | null
columnGap: number | null
padding: Sides
element: HTMLElement
}

export type ElementOrParent = 'parent' | 'element'

export function getGridMeasurementHelperData(
elementPath: ElementPath,
scale: number,
source: ElementOrParent,
): GridMeasurementHelperData | undefined {
return getFromElement(elementPath, gridMeasurementHelperDataFromElement(scale), source)
}

function getStylingSubset(styling: CSSStyleDeclaration): CSSProperties {
// Fields chosen to not overlap with any others, so as to not make React complain.
return {
gridAutoFlow: styling.gridAutoFlow,
gridAutoColumns: styling.gridAutoColumns,
gridAutoRows: styling.gridAutoRows,
gridTemplateColumns: styling.gridTemplateColumns,
gridTemplateRows: styling.gridTemplateRows,
gridColumn: styling.gridColumn,
gridRow: styling.gridRow,
gap: styling.gap,
rowGap: styling.rowGap,
columnGap: styling.columnGap,
justifyContent: styling.justifyContent,
alignContent: styling.alignContent,
padding: styling.padding,
paddingTop: styling.paddingTop,
paddingLeft: styling.paddingLeft,
paddingBottom: styling.paddingBottom,
paddingRight: styling.paddingRight,
borderTop: styling.borderTopWidth,
borderLeft: styling.borderLeftWidth,
borderBottom: styling.borderBottomWidth,
borderRight: styling.borderRightWidth,
}
}

export function gridMeasurementHelperDataFromElement(
scale: number,
): (element: HTMLElement) => GridMeasurementHelperData | undefined {
return (element) => {
const canvasRootContainer = document.getElementById(CanvasContainerID)
if (canvasRootContainer == null) {
return undefined
}

const computedStyle = getComputedStyle(element)
const boundingRectangle = element.getBoundingClientRect()

const computedStyling: CSSProperties = getStylingSubset(computedStyle)

const containerGridProperties = getGridContainerProperties(computedStyle)
const containerGridPropertiesFromProps = getGridContainerProperties(element.style)

const columns = getCellsCount(containerGridProperties.gridTemplateColumns)
const rows = getCellsCount(containerGridProperties.gridTemplateRows)
const borderTopWidth = parseCSSLength(computedStyle.borderTopWidth)
const borderRightWidth = parseCSSLength(computedStyle.borderRightWidth)
const borderBottomWidth = parseCSSLength(computedStyle.borderBottomWidth)
const borderLeftWidth = parseCSSLength(computedStyle.borderLeftWidth)
const border: BorderWidths = {
top: isRight(borderTopWidth) ? borderTopWidth.value.value : 0,
right: isRight(borderRightWidth) ? borderRightWidth.value.value : 0,
bottom: isRight(borderBottomWidth) ? borderBottomWidth.value.value : 0,
left: isRight(borderLeftWidth) ? borderLeftWidth.value.value : 0,
}
const padding = defaultEither(
sides(undefined, undefined, undefined, undefined),
applicative4Either(
applicativeSidesPxTransform,
parseCSSLength(computedStyle.paddingTop),
parseCSSLength(computedStyle.paddingRight),
parseCSSLength(computedStyle.paddingBottom),
parseCSSLength(computedStyle.paddingLeft),
),
)
const gap = defaultEither(
null,
mapEither((n) => n.value, parseCSSLength(computedStyle.gap)),
)

const rowGap = defaultEither(
null,
mapEither((n) => n.value, parseCSSLength(computedStyle.rowGap)),
)

const columnGap = defaultEither(
null,
mapEither((n) => n.value, parseCSSLength(computedStyle.columnGap)),
)

const elementRect = domRectToScaledCanvasRectangle(
boundingRectangle,
1 / scale,
getRoundingFn('nearest-half'),
)
const parentRect = domRectToScaledCanvasRectangle(
canvasRootContainer.getBoundingClientRect(),
1 / scale,
getRoundingFn('nearest-half'),
)
const frame = Utils.offsetRect(elementRect, Utils.negate(parentRect))

return {
frame: frame,
gridTemplateColumns: containerGridProperties.gridTemplateColumns,
gridTemplateRows: containerGridProperties.gridTemplateRows,
gridTemplateColumnsFromProps: containerGridPropertiesFromProps.gridTemplateColumns,
gridTemplateRowsFromProps: containerGridPropertiesFromProps.gridTemplateRows,
border: border,
padding: padding,
gap: gap,
rowGap: rowGap,
columnGap: columnGap,
rows: rows,
columns: columns,
cells: columns * rows,
computedStyling: computedStyling,
element: element,
}
}
}

function useObserversToWatch(elementPathOrPaths: Array<ElementPath> | ElementPath): number {
// Used to trigger extra renders.
const [counter, setCounter] = React.useState(0)
const bumpCounter = React.useCallback(() => {
setCounter((value) => value + 1)
}, [])

// Need to use the mount count for the callback trigger.
const mountCount = useEditorState(
Substores.canvas,
(store) => store.editor.canvas.mountCount,
'useObserversToWatch mountCount',
)

React.useEffect(() => {
// Add the change callback(s) for the element path or paths.
if (Array.isArray(elementPathOrPaths)) {
for (const elementPath of elementPathOrPaths) {
addChangeCallback(mountCount, elementPath, bumpCounter)
}
} else {
addChangeCallback(mountCount, elementPathOrPaths, bumpCounter)
}

return function cleanup() {
if (Array.isArray(elementPathOrPaths)) {
for (const elementPath of elementPathOrPaths) {
removeChangeCallback(elementPath, bumpCounter)
}
} else {
removeChangeCallback(elementPathOrPaths, bumpCounter)
}
}
}, [mountCount, elementPathOrPaths, bumpCounter])

return counter
}

export function useGridMeasurementHelperData(
elementPath: ElementPath,
source: ElementOrParent,
): GridMeasurementHelperData | undefined {
const scale = useEditorState(
Substores.canvas,
(store) => store.editor.canvas.scale,
'useGridMeasurementHelperData scale',
)

useMonitorChangesToElements([elementPath])

useObserversToWatch(elementPath)

return useKeepReferenceEqualityIfPossible(
getGridMeasurementHelperData(elementPath, scale, source),
)
}

export type GridData = GridMeasurementHelperData & {
identifier: GridIdentifier
}

export function useGridData(gridIdentifiers: GridIdentifier[]): GridData[] {
const scale = useEditorState(
Substores.canvas,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { CSSProperties } from 'react'
import type { GridMeasurementHelperData } from './grid-controls-for-strategies'
import type { GridMeasurementHelperData } from './grid-measurements'

export function getGridHelperStyleMatchingTargetGrid(
grid: GridMeasurementHelperData,
Expand Down
Loading
Loading