Skip to content

Commit 1aa3387

Browse files
authoredDec 5, 2024··
Adjust ruler marker resize for spanning elements (#6703)
**Problem:** Ruler marker resize needs some extra tweaks for spanning elements. **Fix:** 1. Make it possible to resize correctly a flow item that spans from the start by dragging its end marker 2. Make it possible to resize correctly a flow item that spans from the end by dragging its start/end markers and treat them as a more traditional shrink/expand Also added specific tests for span combinations. **Manual Tests:** I hereby swear that: - [x] I opened a hydrogen project and it loaded - [x] I could navigate to various routes in Play mode Fixes #6701
1 parent 2a4e2c0 commit 1aa3387

File tree

2 files changed

+216
-4
lines changed

2 files changed

+216
-4
lines changed
 

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

+180
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
} from '../../../../utils/utils.test-utils'
66
import {
77
RulerMarkerColumnEndTestId,
8+
RulerMarkerColumnStartTestId,
89
RulerMarkerRowEndTestId,
910
RulerMarkerRowStartTestId,
1011
} from '../../controls/grid-controls'
@@ -155,6 +156,127 @@ describe('grid resize element ruler strategy', () => {
155156
expect(element.style.gridColumn).toBe('span 2')
156157
expect(element.style.gridRow).toBe('auto')
157158
})
159+
160+
describe('spans', () => {
161+
it('can resize a flow spanning element (start, from the right)', async () => {
162+
const renderResult = await renderTestEditorWithCode(
163+
ProjectCodeWithSpans,
164+
'await-first-dom-report',
165+
)
166+
167+
await selectComponentsForTest(renderResult, [EP.fromString('sb/grid/flow1')])
168+
169+
const control = await renderResult.renderedDOM.findByTestId(RulerMarkerColumnEndTestId)
170+
const controlRect = control.getBoundingClientRect()
171+
const startPoint = { x: controlRect.x + 5, y: controlRect.y + 5 }
172+
const endPoint = { x: controlRect.x + 200, y: controlRect.y + 5 }
173+
await mouseDownAtPoint(control, startPoint)
174+
await mouseMoveToPoint(control, endPoint)
175+
await mouseUpAtPoint(control, endPoint)
176+
177+
const element = await renderResult.renderedDOM.findByTestId('flow1')
178+
expect(element.style.gridColumn).toBe('span 3')
179+
expect(element.style.gridRow).toBe('auto')
180+
})
181+
182+
it('can resize a flow spanning element (start, from the left)', async () => {
183+
const renderResult = await renderTestEditorWithCode(
184+
ProjectCodeWithSpans,
185+
'await-first-dom-report',
186+
)
187+
188+
await selectComponentsForTest(renderResult, [EP.fromString('sb/grid/flow1')])
189+
190+
const control = await renderResult.renderedDOM.findByTestId(RulerMarkerColumnStartTestId)
191+
const controlRect = control.getBoundingClientRect()
192+
const startPoint = { x: controlRect.x + 5, y: controlRect.y + 5 }
193+
const endPoint = { x: controlRect.x + 200, y: controlRect.y + 5 }
194+
await mouseDownAtPoint(control, startPoint)
195+
await mouseMoveToPoint(control, endPoint)
196+
await mouseUpAtPoint(control, endPoint)
197+
198+
const element = await renderResult.renderedDOM.findByTestId('flow1')
199+
expect(element.style.gridColumn).toBe('')
200+
expect(element.style.gridColumnStart).toBe('')
201+
expect(element.style.gridColumnEnd).toBe('3')
202+
expect(element.style.gridRow).toBe('auto')
203+
})
204+
205+
it('can resize a flow spanning element (end)', async () => {
206+
const renderResult = await renderTestEditorWithCode(
207+
ProjectCodeWithSpans,
208+
'await-first-dom-report',
209+
)
210+
211+
await selectComponentsForTest(renderResult, [EP.fromString('sb/grid/flow2')])
212+
213+
// expand to the right
214+
{
215+
const control = await renderResult.renderedDOM.findByTestId(RulerMarkerColumnEndTestId)
216+
const controlRect = control.getBoundingClientRect()
217+
const startPoint = { x: controlRect.x + 5, y: controlRect.y + 5 }
218+
const endPoint = { x: controlRect.x + 200, y: controlRect.y + 5 }
219+
await mouseDownAtPoint(control, startPoint)
220+
await mouseMoveToPoint(control, endPoint)
221+
await mouseUpAtPoint(control, endPoint)
222+
223+
const element = await renderResult.renderedDOM.findByTestId('flow2')
224+
expect(element.style.gridColumn).toBe('2 / span 3')
225+
expect(element.style.gridRow).toBe('auto')
226+
}
227+
228+
// shrink from the left
229+
{
230+
const control = await renderResult.renderedDOM.findByTestId(RulerMarkerColumnStartTestId)
231+
const controlRect = control.getBoundingClientRect()
232+
const startPoint = { x: controlRect.x + 5, y: controlRect.y + 5 }
233+
const endPoint = { x: controlRect.x + 200, y: controlRect.y + 5 }
234+
await mouseDownAtPoint(control, startPoint)
235+
await mouseMoveToPoint(control, endPoint)
236+
await mouseUpAtPoint(control, endPoint)
237+
238+
const element = await renderResult.renderedDOM.findByTestId('flow2')
239+
expect(element.style.gridColumn).toBe('3 / span 2')
240+
expect(element.style.gridRow).toBe('auto')
241+
}
242+
243+
// expand back from the left
244+
{
245+
const control = await renderResult.renderedDOM.findByTestId(RulerMarkerColumnStartTestId)
246+
const controlRect = control.getBoundingClientRect()
247+
const startPoint = { x: controlRect.x + 5, y: controlRect.y + 5 }
248+
const endPoint = { x: controlRect.x - 400, y: controlRect.y + 5 }
249+
await mouseDownAtPoint(control, startPoint)
250+
await mouseMoveToPoint(control, endPoint)
251+
await mouseUpAtPoint(control, endPoint)
252+
253+
const element = await renderResult.renderedDOM.findByTestId('flow2')
254+
expect(element.style.gridColumn).toBe('1 / span 4')
255+
expect(element.style.gridRow).toBe('auto')
256+
}
257+
})
258+
259+
it('can resize a pinned spanning element', async () => {
260+
const renderResult = await renderTestEditorWithCode(
261+
ProjectCodeWithSpans,
262+
'await-first-dom-report',
263+
)
264+
265+
await selectComponentsForTest(renderResult, [EP.fromString('sb/grid/pinned')])
266+
267+
const control = await renderResult.renderedDOM.findByTestId(RulerMarkerColumnEndTestId)
268+
const controlRect = control.getBoundingClientRect()
269+
const startPoint = { x: controlRect.x + 5, y: controlRect.y + 5 }
270+
const endPoint = { x: controlRect.x + 200, y: controlRect.y + 5 }
271+
await mouseDownAtPoint(control, startPoint)
272+
await mouseMoveToPoint(control, endPoint)
273+
await mouseUpAtPoint(control, endPoint)
274+
275+
const element = await renderResult.renderedDOM.findByTestId('pinned')
276+
expect(element.style.gridColumn).toBe('2 / span 3')
277+
expect(element.style.gridRow).toBe('3')
278+
})
279+
})
158280
})
159281

160282
const ProjectCode = `
@@ -232,3 +354,61 @@ export var storyboard = (
232354
</Storyboard>
233355
)
234356
`
357+
358+
const ProjectCodeWithSpans = `
359+
import * as React from 'react'
360+
import { Storyboard } from 'utopia-api'
361+
362+
export var storyboard = (
363+
<Storyboard data-uid='sb'>
364+
<div
365+
style={{
366+
backgroundColor: '#aaaaaa33',
367+
position: 'absolute',
368+
left: 210,
369+
top: 80,
370+
width: 690,
371+
height: 459,
372+
display: 'grid',
373+
gap: 10,
374+
gridTemplateColumns: '1fr 1fr 1fr 1fr',
375+
gridTemplateRows: '1fr 1fr 1fr 1fr',
376+
}}
377+
data-uid='grid'
378+
data-testid='grid'
379+
>
380+
<div
381+
style={{
382+
backgroundColor: '#09f',
383+
alignSelf: 'stretch',
384+
justifySelf: 'stretch',
385+
gridColumn: 'span 2',
386+
}}
387+
data-uid='flow1'
388+
data-testid='flow1'
389+
/>
390+
<div
391+
style={{
392+
backgroundColor: '#f90',
393+
alignSelf: 'stretch',
394+
justifySelf: 'stretch',
395+
gridColumn: '2 / span 2',
396+
}}
397+
data-uid='flow2'
398+
data-testid='flow2'
399+
/>
400+
<div
401+
style={{
402+
backgroundColor: '#f09',
403+
gridColumn: '2 / span 2',
404+
gridRow: 3,
405+
alignSelf: 'stretch',
406+
justifySelf: 'stretch',
407+
}}
408+
data-uid='pinned'
409+
data-testid='pinned'
410+
/>
411+
</div>
412+
</Storyboard>
413+
)
414+
`

‎editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-ruler-strategy.ts

+36-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ import type {
55
GridElementProperties,
66
GridPositionOrSpan,
77
} from '../../../../core/shared/element-template'
8-
import { gridPositionValue, isGridPositionValue } from '../../../../core/shared/element-template'
8+
import {
9+
gridPositionValue,
10+
isGridPositionValue,
11+
isGridSpan,
12+
} from '../../../../core/shared/element-template'
913
import type { CanvasPoint, CanvasRectangle, CanvasVector } from '../../../../core/shared/math-utils'
1014
import { canvasRectangle, isInfinityRectangle } from '../../../../core/shared/math-utils'
1115
import { assertNever } from '../../../../core/shared/utils'
@@ -160,8 +164,18 @@ export const gridResizeElementRulerStrategy: CanvasStrategyFactory = (
160164
closestVertical,
161165
)
162166

163-
const columnCount = getCellsCount(resizedProps.gridColumnStart, resizedProps.gridColumnEnd)
164-
const rowCount = getCellsCount(resizedProps.gridRowStart, resizedProps.gridRowEnd)
167+
const columnCount = getCellsCount(
168+
resizedProps.gridColumnStart,
169+
resizedProps.gridColumnEnd,
170+
bounds.column,
171+
bounds.width,
172+
)
173+
const rowCount = getCellsCount(
174+
resizedProps.gridRowStart,
175+
resizedProps.gridRowEnd,
176+
bounds.row,
177+
bounds.height,
178+
)
165179

166180
const normalizedGridProps: GridElementProperties = {
167181
gridColumnStart: normalizeGridElementPositionAfterResize(
@@ -295,12 +309,30 @@ function getResizedElementProperties(
295309
function getCellsCount(
296310
start: GridPositionOrSpan | null,
297311
end: GridPositionOrSpan | null,
312+
originalStart: number,
313+
originalSize: number,
298314
): number | null {
315+
// start is a number
299316
if (isGridPositionValue(start) && start.numericalPosition != null) {
317+
// end is also a number, return the difference
300318
if (isGridPositionValue(end) && end.numericalPosition != null) {
301319
return end.numericalPosition - start.numericalPosition
302320
}
303-
return start.numericalPosition
321+
322+
// end is a discrete span, recalculate the size as a shrink or an expansion of the original size
323+
if (isGridSpan(end) && end.type === 'SPAN_NUMERIC') {
324+
return originalSize + (originalStart - start.numericalPosition)
325+
}
304326
}
327+
328+
// start is a discrete span
329+
if (isGridSpan(start) && start.type === 'SPAN_NUMERIC') {
330+
// end is a number, return the max between the span size and the start position
331+
if (isGridPositionValue(end) && end.numericalPosition != null) {
332+
return Math.max(end.numericalPosition, start.value) - 1
333+
}
334+
}
335+
336+
// nothing specific found, will be handled with a separate fallback
305337
return null
306338
}

0 commit comments

Comments
 (0)
Please sign in to comment.