Skip to content

Commit

Permalink
pass in snap targets from the strategies (#4357)
Browse files Browse the repository at this point in the history
* pass in snap targets from the strategies

* helpers

* whoops

* snap to children

* whoops 2

* refactor snapPoint to happy path to the left

* only snap to absolute chilren

* tests

* keyboard resize snaps to children
  • Loading branch information
bkrmendy authored Oct 13, 2023
1 parent 1ea7e5a commit 7119cc1
Show file tree
Hide file tree
Showing 10 changed files with 380 additions and 466 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1470,6 +1470,164 @@ export var storyboard = (
altModifier,
)
})
describe('snapping to pinned children', () => {
it('container does not snap to flow child', async () => {
const renderResult = await renderTestEditorWithCode(
makeTestProjectCodeWithSnippet(`
<div style={{ width: '100%', height: '100%' }} data-uid='aaa'>
<div
style={{ backgroundColor: '#aaaaaa33', position: 'absolute', left: 40, top: 50, width: 200, height: 120 }}
data-uid='bbb'
data-testid='bbb'
>
<div style={{ width: 20, height: 20, backgroundColor: '#d0e5fc' }} data-uid='42c' />
</div>
</div>
`),
'await-first-dom-report',
)

const target = EP.appendNewElementPath(TestScenePath, ['aaa', 'bbb'])

await renderResult.dispatch([selectComponents([target], false)], true)
await doSnapDrag(renderResult, { x: 0, y: 20 }, EdgePositionTop, async () => {
// no guidelines are shown
expect(
renderResult.getEditorState().editor.canvas.controls.snappingGuidelines.length,
).toEqual(0)
})

await renderResult.getDispatchFollowUpActionsFinished()
expect(getPrintedUiJsCode(renderResult.getEditorState())).toEqual(
makeTestProjectCodeWithSnippet(`
<div style={{ width: '100%', height: '100%' }} data-uid='aaa'>
<div
style={{ backgroundColor: '#aaaaaa33', position: 'absolute', left: 40, top: 70, width: 200, height: 100 }}
data-uid='bbb'
data-testid='bbb'
>
<div style={{ width: 20, height: 20, backgroundColor: '#d0e5fc' }} data-uid='42c' />
</div>
</div>
`),
)
})
it('container does not snap to flex child', async () => {
const renderResult = await renderTestEditorWithCode(
makeTestProjectCodeWithSnippet(`
<div style={{ width: '100%', height: '100%' }} data-uid='aaa'>
<div
style={{ backgroundColor: '#aaaaaa33', position: 'absolute', left: 40, top: 50, width: 200, height: 120, display: 'flex' }}
data-uid='bbb'
data-testid='bbb'
>
<div style={{ width: 20, height: 20, backgroundColor: '#d0e5fc' }} data-uid='42c' />
</div>
</div>
`),
'await-first-dom-report',
)

const target = EP.appendNewElementPath(TestScenePath, ['aaa', 'bbb'])

await renderResult.dispatch([selectComponents([target], false)], true)
await doSnapDrag(renderResult, { x: 0, y: 20 }, EdgePositionTop, async () => {
// no guidelines are shown
expect(
renderResult.getEditorState().editor.canvas.controls.snappingGuidelines.length,
).toEqual(0)
})

await renderResult.getDispatchFollowUpActionsFinished()
expect(getPrintedUiJsCode(renderResult.getEditorState())).toEqual(
makeTestProjectCodeWithSnippet(`
<div style={{ width: '100%', height: '100%' }} data-uid='aaa'>
<div
style={{ backgroundColor: '#aaaaaa33', position: 'absolute', left: 40, top: 70, width: 200, height: 100, display: 'flex' }}
data-uid='bbb'
data-testid='bbb'
>
<div style={{ width: 20, height: 20, backgroundColor: '#d0e5fc' }} data-uid='42c' />
</div>
</div>
`),
)
})
;(
[
[windowPoint({ y: 0, x: 10 }), EdgePositionLeft, 'left', 1],
[windowPoint({ y: 10, x: 0 }), EdgePositionTop, 'top', 1],
[windowPoint({ y: 10, x: 10 }), EdgePositionTopLeft, 'top left', 2],
[windowPoint({ y: 0, x: -20 }), EdgePositionRight, 'right', 0],
[windowPoint({ y: -20, x: 0 }), EdgePositionBottom, 'bottom', 0],
[windowPoint({ y: -20, x: -20 }), EdgePositionBottomRight, 'bottom right', 0],
] as const
).forEach(([delta, edge, label, numberOfGuidelines]) => {
it(`${numberOfGuidelines} snap lines shown when resizing container with absolte child pinned to bottom and right - dragging from ${label}`, async () => {
const renderResult = await renderTestEditorWithCode(
makeTestProjectCodeWithSnippet(`
<div style={{ width: '100%', height: '100%' }} data-uid='aaa'>
<div
style={{ backgroundColor: '#aaaaaa33', position: 'absolute', left: 50, top: 50, width: 50, height: 50 }}
data-uid='bbb'
data-testid='bbb'
>
<div style={{ width: 20, height: 20, backgroundColor: '#d0e5fc', position: 'absolute', right: 20, bottom: 20, }} data-uid='42c' />
</div>
</div>
`),
'await-first-dom-report',
)

const target = EP.appendNewElementPath(TestScenePath, ['aaa', 'bbb'])

await renderResult.dispatch([selectComponents([target], false)], true)
await doSnapDrag(renderResult, delta, edge, async () => {
// guidelines are shown
expect(
renderResult.getEditorState().editor.canvas.controls.snappingGuidelines.length,
).toEqual(numberOfGuidelines)
})
})
})
;(
[
[windowPoint({ y: 0, x: 20 }), EdgePositionLeft, 'left', 0],
[windowPoint({ y: 20, x: 0 }), EdgePositionTop, 'top', 0],
[windowPoint({ y: 20, x: 10 }), EdgePositionTopLeft, 'top left', 0],
[windowPoint({ y: 0, x: -10 }), EdgePositionRight, 'right', 1],
[windowPoint({ y: -10, x: 0 }), EdgePositionBottom, 'bottom', 1],
[windowPoint({ y: -10, x: -10 }), EdgePositionBottomRight, 'bottom right', 2],
] as const
).forEach(([delta, edge, label, numberOfGuidelines]) => {
it(`${numberOfGuidelines} snap lines shown when resizing container with absolte child pinned to top and left - dragging from ${label}`, async () => {
const renderResult = await renderTestEditorWithCode(
makeTestProjectCodeWithSnippet(`
<div style={{ width: '100%', height: '100%' }} data-uid='aaa'>
<div
style={{ backgroundColor: '#aaaaaa33', position: 'absolute', left: 50, top: 50, width: 50, height: 50 }}
data-uid='bbb'
data-testid='bbb'
>
<div style={{ width: 20, height: 20, backgroundColor: '#d0e5fc', position: 'absolute', top: 20, left: 20, }} data-uid='42c' />
</div>
</div>
`),
'await-first-dom-report',
)

const target = EP.appendNewElementPath(TestScenePath, ['aaa', 'bbb'])

await renderResult.dispatch([selectComponents([target], false)], true)
await doSnapDrag(renderResult, delta, edge, async () => {
// guidelines are shown
expect(
renderResult.getEditorState().editor.canvas.controls.snappingGuidelines.length,
).toEqual(numberOfGuidelines)
})
})
})
})

describe('groups', () => {
AllFragmentLikeTypes.forEach((type) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { setCursorCommand } from '../../commands/set-cursor-command'
import { setElementsToRerenderCommand } from '../../commands/set-elements-to-rerender-command'
import { setSnappingGuidelines } from '../../commands/set-snapping-guidelines-command'
import { updateHighlightedViews } from '../../commands/update-highlighted-views-command'
import { gatherParentAndSiblingTargets } from '../../controls/guideline-helpers'
import { ImmediateParentBounds } from '../../controls/parent-bounds'
import { ImmediateParentOutlines } from '../../controls/parent-outlines'
import { AbsoluteResizeControl } from '../../controls/select-mode/absolute-resize-control'
Expand Down Expand Up @@ -56,7 +57,7 @@ import {
} from './resize-helpers'
import type { EnsureFramePointsExist } from './resize-strategy-helpers'
import { createResizeCommandsFromFrame } from './resize-strategy-helpers'
import { runLegacyAbsoluteResizeSnapping } from './shared-absolute-resize-strategy-helpers'
import { childrenBoundsToSnapTo, snapBoundingBox } from './shared-absolute-resize-strategy-helpers'
import { flattenSelection, getMultiselectBounds } from './shared-move-strategies-helpers'

export function absoluteResizeBoundingBoxStrategy(
Expand Down Expand Up @@ -153,7 +154,22 @@ export function absoluteResizeBoundingBoxStrategy(
lockedAspectRatio,
centerBased,
)
const parentAndSiblings: ElementPath[] = gatherParentAndSiblingTargets(
canvasState.startingMetadata,
canvasState.startingAllElementProps,
canvasState.startingElementPathTree,
originalTargets,
)
const childrenToSnapTo = childrenBoundsToSnapTo(
edgePosition,
originalTargets,
canvasState.startingMetadata,
canvasState.startingAllElementProps,
canvasState.startingElementPathTree,
)
const snapTargets = [...childrenToSnapTo, ...parentAndSiblings]
const { snappedBoundingBox, guidelinesWithSnappingVector } = snapBoundingBox(
snapTargets,
originalTargets,
canvasState.startingMetadata,
edgePosition,
Expand Down Expand Up @@ -492,32 +508,3 @@ function getBoundDimension(
return null
}
}

function snapBoundingBox(
selectedElements: Array<ElementPath>,
jsxMetadata: ElementInstanceMetadataMap,
edgePosition: EdgePosition,
resizedBounds: CanvasRectangle,
canvasScale: number,
lockedAspectRatio: number | null,
centerBased: 'center-based' | 'non-center-based',
allElementProps: AllElementProps,
pathTrees: ElementPathTrees,
) {
const { snappedBoundingBox, guidelinesWithSnappingVector } = runLegacyAbsoluteResizeSnapping(
selectedElements,
jsxMetadata,
edgePosition,
resizedBounds,
canvasScale,
lockedAspectRatio,
centerBased,
allElementProps,
pathTrees,
)

return {
snappedBoundingBox,
guidelinesWithSnappingVector,
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { MetadataUtils } from '../../../../core/model/element-metadata-utils'
import { Keyboard, KeyCharacter } from '../../../../utils/keyboard'
import { Keyboard } from '../../../../utils/keyboard'
import type { CanvasStrategy, InteractionCanvasState } from '../canvas-strategy-types'
import {
emptyStrategyApplicationResult,
Expand All @@ -8,7 +8,6 @@ import {
} from '../canvas-strategy-types'
import type { CanvasVector } from '../../../../core/shared/math-utils'
import {
CanvasRectangle,
canvasRectangle,
offsetPoint,
offsetRect,
Expand Down Expand Up @@ -36,6 +35,7 @@ import { honoursPropsPosition } from './absolute-utils'
import type { InteractionSession } from '../interaction-state'
import type { ElementPath } from '../../../../core/shared/project-file-types'
import { retargetStrategyToChildrenOfFragmentLikeElements } from './fragment-like-helpers'
import { gatherParentAndSiblingTargets } from '../../controls/guideline-helpers'

export function keyboardAbsoluteMoveStrategy(
canvasState: InteractionCanvasState,
Expand Down Expand Up @@ -93,7 +93,13 @@ export function keyboardAbsoluteMoveStrategy(
keyboardMovement,
)

const guidelines = getKeyboardStrategyGuidelines(canvasState, interactionSession, newFrame)
const snapTargets: ElementPath[] = gatherParentAndSiblingTargets(
canvasState.startingMetadata,
canvasState.startingAllElementProps,
canvasState.startingElementPathTree,
getTargetPathsFromInteractionTarget(canvasState.interactionTarget),
)
const guidelines = getKeyboardStrategyGuidelines(snapTargets, interactionSession, newFrame)

commands.push(setSnappingGuidelines('mid-interaction', guidelines))
commands.push(pushIntendedBoundsAndUpdateGroups(intendedBounds, 'starting-metadata'))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,15 @@ import type { CanvasStrategy, InteractionCanvasState } from '../canvas-strategy-
import {
controlWithProps,
emptyStrategyApplicationResult,
getTargetPathsFromInteractionTarget,
strategyApplicationResult,
} from '../canvas-strategy-types'
import type { InteractionSession } from '../interaction-state'
import { resizeBoundingBox, supportsAbsoluteResize } from './resize-helpers'
import { createResizeCommands } from './shared-absolute-resize-strategy-helpers'
import {
childrenBoundsToSnapTo,
createResizeCommands,
} from './shared-absolute-resize-strategy-helpers'
import type { AccumulatedPresses } from './shared-keyboard-strategy-helpers'
import {
accumulatePresses,
Expand All @@ -44,6 +48,9 @@ import type { ElementPath } from '../../../../core/shared/project-file-types'
import type { ProjectContentTreeRoot } from '../../../../components/assets'
import type { InspectorStrategy } from '../../../../components/inspector/inspector-strategies/inspector-strategy'
import type { ElementPathTrees } from '../../../../core/shared/element-path-tree'
import { gatherParentAndSiblingTargets } from '../../controls/guideline-helpers'
import { uniqBy } from '../../../../core/shared/array-utils'
import * as EP from '../../../../core/shared/element-path'

interface VectorAndEdge {
movement: CanvasVector
Expand Down Expand Up @@ -278,7 +285,21 @@ export function keyboardAbsoluteResizeStrategy(
intendedBounds = changeBoundsResult.intendedBounds
commands.push(...changeBoundsResult.commands)
})
const guidelines = getKeyboardStrategyGuidelines(canvasState, interactionSession, newFrame)
const parentsAndSiblings: ElementPath[] = gatherParentAndSiblingTargets(
canvasState.startingMetadata,
canvasState.startingAllElementProps,
canvasState.startingElementPathTree,
getTargetPathsFromInteractionTarget(canvasState.interactionTarget),
)
const children = getChildrenToSnapTo(
deduplicateEdges(movementsWithEdges.map((m) => m.edge)),
selectedElements,
canvasState.startingMetadata,
canvasState.startingAllElementProps,
canvasState.startingElementPathTree,
)
const snapTargets = [...children, ...parentsAndSiblings]
const guidelines = getKeyboardStrategyGuidelines(snapTargets, interactionSession, newFrame)
commands.push(setSnappingGuidelines('mid-interaction', guidelines))
commands.push(pushIntendedBoundsAndUpdateGroups(intendedBounds, 'starting-metadata'))
commands.push(setElementsToRerenderCommand(selectedElements))
Expand All @@ -302,3 +323,22 @@ function getEdgePositionFromKey(key: KeyCharacter): EdgePosition | null {
return null
}
}

function deduplicateEdges(edges: EdgePosition[]): EdgePosition[] {
return uniqBy(edges, (l, r) => l.x === r.x && l.y === r.y)
}

function getChildrenToSnapTo(
edgePositions: EdgePosition[],
targets: Array<ElementPath>,
componentMetadata: ElementInstanceMetadataMap,
allElementProps: AllElementProps,
pathTrees: ElementPathTrees,
) {
return uniqBy(
edgePositions.flatMap((edge) =>
childrenBoundsToSnapTo(edge, targets, componentMetadata, allElementProps, pathTrees),
),
(l, r) => EP.toString(l) === EP.toString(r),
)
}
Loading

0 comments on commit 7119cc1

Please sign in to comment.