Skip to content

Commit a53ecb5

Browse files
authored
refactor(grid) Rework SubduedGridGapControl (#6564)
- Removed a lot of the contents of `SubduedGridGapControl` and replace the main contents of the rendering with `GridPaddingOutlineForDimension`. - Added `draggedOutlineColor` to `GridPaddingOutlineForDimension` and `GridRowHighlight`. - Removed `gridGapControlBoundsFromMetadata`.
1 parent f9cea56 commit a53ecb5

File tree

3 files changed

+46
-225
lines changed

3 files changed

+46
-225
lines changed

editor/src/components/canvas/controls/select-mode/grid-gap-control-component.tsx

+10-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type { ElementPath } from '../../../../core/shared/project-file-types'
88
import { assertNever } from '../../../../core/shared/utils'
99
import { Modifier } from '../../../../utils/modifiers'
1010
import { when } from '../../../../utils/react-conditionals'
11+
import type { UtopiColor } from '../../../../uuiui'
1112
import { useColorTheme, UtopiaStyles } from '../../../../uuiui'
1213
import { CSSCursor } from '../../../../uuiui-deps'
1314
import type { EditorDispatch } from '../../../editor/action-types'
@@ -139,7 +140,7 @@ export const GridGapControlComponent = React.memo<GridGapControlProps>((props) =
139140
)
140141
})
141142

142-
const GridPaddingOutlineForDimension = (props: {
143+
export const GridPaddingOutlineForDimension = (props: {
143144
grid: GridData
144145
dimension: 'rows' | 'columns'
145146
onMouseDown: (e: React.MouseEvent<HTMLDivElement>) => void
@@ -148,6 +149,7 @@ const GridPaddingOutlineForDimension = (props: {
148149
zIndexPriority: boolean
149150
gridGap: CSSNumberWithRenderedValue
150151
elementHovered: boolean
152+
draggedOutlineColor?: UtopiColor
151153
}) => {
152154
const {
153155
grid,
@@ -158,6 +160,7 @@ const GridPaddingOutlineForDimension = (props: {
158160
onMouseOver,
159161
zIndexPriority,
160162
elementHovered,
163+
draggedOutlineColor,
161164
} = props
162165

163166
let style: CSSProperties = {
@@ -199,6 +202,7 @@ const GridPaddingOutlineForDimension = (props: {
199202
beingDragged={beingDragged}
200203
onMouseOver={onMouseOver}
201204
elementHovered={elementHovered}
205+
draggedOutlineColor={draggedOutlineColor}
202206
/>
203207
)
204208
})}
@@ -218,6 +222,7 @@ const GridRowHighlight = (props: {
218222
beingDragged: boolean
219223
onMouseOver: () => void
220224
elementHovered: boolean
225+
draggedOutlineColor?: UtopiColor
221226
}) => {
222227
const {
223228
gapId,
@@ -231,6 +236,7 @@ const GridRowHighlight = (props: {
231236
beingDragged,
232237
onMouseOver,
233238
elementHovered,
239+
draggedOutlineColor,
234240
} = props
235241

236242
const colorTheme = useColorTheme()
@@ -242,7 +248,9 @@ const GridRowHighlight = (props: {
242248

243249
const lineWidth = 1 / canvasScale
244250

245-
const outlineColor = beingDragged ? colorTheme.brandNeonOrange.value : 'transparent'
251+
const outlineColor = beingDragged
252+
? (draggedOutlineColor ?? colorTheme.brandNeonOrange).value
253+
: 'transparent'
246254

247255
const [backgroundShown, setBackgroundShown] = React.useState<boolean>(false)
248256

Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
import React from 'react'
22
import { useColorTheme } from '../../../../uuiui'
3-
import { Substores, useEditorState, useRefEditorState } from '../../../editor/store/store-hook'
4-
import { useBoundingBox } from '../bounding-box-hooks'
3+
import { Substores, useEditorState } from '../../../editor/store/store-hook'
54
import { CanvasOffsetWrapper } from '../canvas-offset-wrapper'
65
import type { Axis } from '../../gap-utils'
7-
import { gridGapControlBoundsFromMetadata, maybeGridGapData } from '../../gap-utils'
8-
import type { ElementPath } from 'utopia-shared/src/types'
96
import { useGridData } from '../grid-controls-for-strategies'
10-
import { fallbackEmptyValue } from './controls-common'
11-
import type { CanvasRectangle } from '../../../../core/shared/math-utils'
12-
import type { CSSNumber } from '../../../../components/inspector/common/css-utils'
7+
import { unitlessCSSNumberWithRenderedValue } from './controls-common'
8+
import { NO_OP } from '../../../../core/shared/utils'
9+
import * as EP from '../../../../core/shared/element-path'
10+
import { GridPaddingOutlineForDimension } from './grid-gap-control-component'
1311

1412
export interface SubduedGridGapControlProps {
1513
hoveredOrFocused: 'hovered' | 'focused'
@@ -18,124 +16,49 @@ export interface SubduedGridGapControlProps {
1816

1917
export const SubduedGridGapControl = React.memo<SubduedGridGapControlProps>((props) => {
2018
const { hoveredOrFocused, axis } = props
19+
const colorTheme = useColorTheme()
2120
const targets = useEditorState(
2221
Substores.selectedViews,
2322
(store) => store.editor.selectedViews,
2423
'SubduedGridGapControl selectedViews',
2524
)
2625

27-
const metadata = useRefEditorState((store) => store.editor.jsxMetadata)
28-
const selectedElement = targets.at(0)
29-
3026
const gridRowColumnInfo = useGridData(targets)
31-
const selectedGrid = gridRowColumnInfo.at(0)
32-
33-
const filteredGaps = React.useMemo(() => {
34-
if (selectedElement == null || selectedGrid == null) {
35-
return []
36-
}
37-
const gridGap = maybeGridGapData(metadata.current, selectedElement)
38-
if (gridGap == null) {
39-
return []
40-
}
41-
42-
const gridGapRow = gridGap.row
43-
const gridGapColumn = gridGap.column
4427

45-
const controlBounds = gridGapControlBoundsFromMetadata(selectedGrid, {
46-
row: fallbackEmptyValue(gridGapRow),
47-
column: fallbackEmptyValue(gridGapColumn),
48-
})
49-
return controlBounds.gaps.filter((gap) => gap.axis === axis || axis === 'both')
50-
}, [axis, metadata, selectedElement, selectedGrid])
51-
52-
if (filteredGaps.length === 0 || selectedElement == null) {
28+
if (gridRowColumnInfo.length === 0) {
5329
return null
5430
}
5531

56-
return (
57-
<>
58-
{filteredGaps.map((gap) => (
59-
<GridGapControl
60-
key={gap.gapId}
61-
targets={targets}
62-
selectedElement={selectedElement}
63-
hoveredOrFocused={hoveredOrFocused}
64-
gap={gap}
65-
/>
66-
))}
67-
</>
68-
)
69-
})
70-
71-
function GridGapControl({
72-
targets,
73-
selectedElement,
74-
hoveredOrFocused,
75-
gap,
76-
}: {
77-
targets: Array<ElementPath>
78-
selectedElement: ElementPath
79-
hoveredOrFocused: 'hovered' | 'focused'
80-
gap: {
81-
bounds: CanvasRectangle
82-
gapId: string
83-
gap: CSSNumber
84-
axis: Axis
85-
}
86-
}) {
87-
const metadata = useRefEditorState((store) => store.editor.jsxMetadata)
88-
const scale = useEditorState(
89-
Substores.canvas,
90-
(store) => store.editor.canvas.scale,
91-
'GridGapControl scale',
92-
)
93-
const gridRowColumnInfo = useGridData([selectedElement])
94-
95-
const sideRef = useBoundingBox([selectedElement], (ref, parentBoundingBox) => {
96-
const gridGap = maybeGridGapData(metadata.current, selectedElement)
97-
const selectedGrid = gridRowColumnInfo.at(0)
98-
if (gridGap == null || selectedGrid == null) {
99-
return
100-
}
101-
102-
const controlBounds = gridGapControlBoundsFromMetadata(selectedGrid, {
103-
row: fallbackEmptyValue(gridGap.row),
104-
column: fallbackEmptyValue(gridGap.column),
105-
})
106-
107-
const bound = controlBounds.gaps.find((updatedGap) => updatedGap.gapId === gap.gapId)
108-
if (bound == null) {
109-
return
110-
}
111-
112-
ref.current.style.display = 'block'
113-
ref.current.style.left = `${bound.bounds.x + parentBoundingBox.x}px`
114-
ref.current.style.top = `${bound.bounds.y + parentBoundingBox.y}px`
115-
ref.current.style.height = numberToPxValue(bound.bounds.height)
116-
ref.current.style.width = numberToPxValue(bound.bounds.width)
117-
})
118-
119-
const color = useColorTheme().brandNeonPink.value
120-
121-
const solidOrDashed = hoveredOrFocused === 'focused' ? 'solid' : 'dashed'
122-
12332
return (
12433
<CanvasOffsetWrapper>
125-
<div
126-
ref={sideRef}
127-
style={{
128-
position: 'absolute',
129-
border: `1px ${solidOrDashed} ${color}`,
130-
}}
131-
data-testid={getSubduedGridGaplTestID(hoveredOrFocused)}
132-
/>
34+
{gridRowColumnInfo.map((gridData) => {
35+
return (
36+
<React.Fragment key={`grid-gap-${EP.toString(gridData.elementPath)}`}>
37+
<GridPaddingOutlineForDimension
38+
grid={gridData}
39+
dimension={'rows'}
40+
onMouseDown={NO_OP}
41+
beingDragged={true}
42+
onMouseOver={NO_OP}
43+
zIndexPriority={false}
44+
gridGap={unitlessCSSNumberWithRenderedValue(gridData.rowGap ?? 0)}
45+
elementHovered={hoveredOrFocused === 'hovered' && axis === 'row'}
46+
draggedOutlineColor={colorTheme.brandNeonPink}
47+
/>
48+
<GridPaddingOutlineForDimension
49+
grid={gridData}
50+
dimension={'columns'}
51+
onMouseDown={NO_OP}
52+
beingDragged={true}
53+
onMouseOver={NO_OP}
54+
zIndexPriority={false}
55+
gridGap={unitlessCSSNumberWithRenderedValue(gridData.columnGap ?? 0)}
56+
elementHovered={hoveredOrFocused === 'hovered' && axis === 'column'}
57+
draggedOutlineColor={colorTheme.brandNeonPink}
58+
/>
59+
</React.Fragment>
60+
)
61+
})}
13362
</CanvasOffsetWrapper>
13463
)
135-
}
136-
137-
export function getSubduedGridGaplTestID(hoveredOrFocused: 'hovered' | 'focused'): string {
138-
return `SubduedGridGapControl-${hoveredOrFocused}`
139-
}
140-
141-
const numberToPxValue = (n: number) => n + 'px'
64+
})

editor/src/components/canvas/gap-utils.ts

-110
Original file line numberDiff line numberDiff line change
@@ -176,116 +176,6 @@ export function gapControlBoundsFromMetadata(
176176
)
177177
}
178178

179-
export function gridGapControlBoundsFromMetadata(
180-
gridRowColumnInfo: GridData,
181-
gapValues: { row: CSSNumber; column: CSSNumber },
182-
): {
183-
gaps: Array<{
184-
bounds: CanvasRectangle
185-
gapId: string
186-
gap: CSSNumber
187-
axis: Axis
188-
}>
189-
rows: number
190-
columns: number
191-
cellBounds: CanvasRectangle
192-
gapValues: { row: CSSNumber; column: CSSNumber }
193-
gridTemplateRows: string
194-
gridTemplateColumns: string
195-
} {
196-
const emptyResult = {
197-
rows: 0,
198-
columns: 0,
199-
gaps: [],
200-
cellBounds: canvasRectangle({ x: 0, y: 0, width: 0, height: 0 }),
201-
gapValues: gapValues,
202-
gridTemplateRows: '1fr',
203-
gridTemplateColumns: '1fr',
204-
}
205-
const grid = gridRowColumnInfo.metadata
206-
207-
if (grid == null) {
208-
return emptyResult
209-
}
210-
211-
const parentGridBounds = grid.globalFrame
212-
213-
if (parentGridBounds == null || isInfinityRectangle(parentGridBounds)) {
214-
return emptyResult
215-
}
216-
217-
const gridRows = gridRowColumnInfo.rows
218-
const gridColumns = gridRowColumnInfo.columns
219-
const gridTemplateRows = getNullableAutoOrTemplateBaseString(gridRowColumnInfo.gridTemplateRows)
220-
const gridTemplateColumns = getNullableAutoOrTemplateBaseString(
221-
gridRowColumnInfo.gridTemplateColumns,
222-
)
223-
224-
const gridCellBounds = grid.specialSizeMeasurements.gridCellGlobalFrames
225-
226-
if (gridCellBounds == null || gridCellBounds.length == 0) {
227-
return emptyResult
228-
}
229-
const allCellsBound = canvasRectangle({
230-
x: gridCellBounds[0][0].x - parentGridBounds.x,
231-
y: gridCellBounds[0][0].y - parentGridBounds.y,
232-
width:
233-
gridCellBounds[0][gridCellBounds[0].length - 1].x +
234-
gridCellBounds[0][gridCellBounds[0].length - 1].width -
235-
gridCellBounds[0][0].x,
236-
height:
237-
gridCellBounds[gridCellBounds.length - 1][0].y +
238-
gridCellBounds[gridCellBounds.length - 1][0].height -
239-
gridCellBounds[0][0].y,
240-
})
241-
242-
// row gaps array
243-
const rowGaps = createArrayWithLength(gridCellBounds.length - 1, (i) => {
244-
// cell i represents the gap between child [i * gridColumns] and child [(i+1) * gridColumns]
245-
const firstChildBounds = gridCellBounds[i][0]
246-
const secondChildBounds = gridCellBounds[i + 1][0]
247-
return {
248-
gapId: `${EP.toString(grid.elementPath)}-row-gap-${i}`,
249-
bounds: canvasRectangle({
250-
x: allCellsBound.x,
251-
y: firstChildBounds.y + firstChildBounds.height - parentGridBounds.y,
252-
width: allCellsBound.width,
253-
height: secondChildBounds.y - firstChildBounds.y - firstChildBounds.height,
254-
}),
255-
gap: gapValues.row,
256-
axis: 'row' as Axis,
257-
}
258-
})
259-
260-
// column gaps array
261-
const columnGaps = createArrayWithLength(gridCellBounds[0].length - 1, (i) => {
262-
// cell i represents the gap between child [i] and child [i + 1]
263-
const firstChildBounds = gridCellBounds[0][i]
264-
const secondChildBounds = gridCellBounds[0][i + 1]
265-
return {
266-
gapId: `${EP.toString(grid.elementPath)}-column-gap-${i}`,
267-
bounds: canvasRectangle({
268-
x: firstChildBounds.x + firstChildBounds.width - parentGridBounds.x,
269-
y: allCellsBound.y,
270-
width: secondChildBounds.x - firstChildBounds.x - firstChildBounds.width,
271-
height: allCellsBound.height,
272-
}),
273-
gap: gapValues.column,
274-
axis: 'column' as Axis,
275-
}
276-
})
277-
278-
return {
279-
gaps: rowGaps.concat(columnGaps),
280-
rows: gridRows,
281-
columns: gridColumns,
282-
gridTemplateRows: gridTemplateRows ?? '',
283-
gridTemplateColumns: gridTemplateColumns ?? '',
284-
cellBounds: allCellsBound,
285-
gapValues: gapValues,
286-
}
287-
}
288-
289179
export interface GridGapData {
290180
row: CSSNumberWithRenderedValue
291181
column: CSSNumberWithRenderedValue

0 commit comments

Comments
 (0)