diff --git a/editor/src/components/canvas/canvas-strategies/canvas-strategy-types.ts b/editor/src/components/canvas/canvas-strategies/canvas-strategy-types.ts index d6c5a6bfebb5..a3c28f5740f6 100644 --- a/editor/src/components/canvas/canvas-strategies/canvas-strategy-types.ts +++ b/editor/src/components/canvas/canvas-strategies/canvas-strategy-types.ts @@ -21,7 +21,13 @@ export interface CustomStrategyState { strategyGeneratedUidsCache: { [elementPath: string]: string | undefined } elementsToRerender: Array action: ActiveFrameAction | null - targetGridCell: GridCellCoordinates | null + grid: GridCustomStrategyState +} + +export type GridCustomStrategyState = { + targetCell: GridCellCoordinates | null + draggingFromCell: GridCellCoordinates | null + rootCell: GridCellCoordinates | null } export type CustomStrategyStatePatch = Partial @@ -34,7 +40,11 @@ export function defaultCustomStrategyState(): CustomStrategyState { strategyGeneratedUidsCache: {}, elementsToRerender: [], action: null, - targetGridCell: null, + grid: { + targetCell: null, + draggingFromCell: null, + rootCell: null, + }, } } diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.ts index 1d9248f19f94..91f863118788 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.ts @@ -1,6 +1,7 @@ import type { ElementPath } from 'utopia-shared/src/types' import { MetadataUtils } from '../../../../core/model/element-metadata-utils' import type { + ElementInstanceMetadata, ElementInstanceMetadataMap, GridElementProperties, } from '../../../../core/shared/element-template' @@ -14,10 +15,11 @@ import { import { create } from '../../../../core/shared/property-path' import type { CanvasCommand } from '../../commands/commands' import { setProperty } from '../../commands/set-property-command' -import type { GridCellCoordinates } from '../../controls/grid-controls' -import { gridCellCoordinates } from '../../controls/grid-controls' import { canvasPointToWindowPoint } from '../../dom-lookup' import type { DragInteractionData } from '../interaction-state' +import type { GridCustomStrategyState } from '../canvas-strategy-types' +import type { GridCellCoordinates } from '../../controls/grid-controls' +import { gridCellCoordinates } from '../../controls/grid-controls' export function getGridCellUnderMouse(mousePoint: WindowPoint) { return getGridCellAtPoint(mousePoint, false) @@ -77,13 +79,21 @@ export function runGridRearrangeMove( interactionData: DragInteractionData, canvasScale: number, canvasOffset: CanvasVector, - targetGridCell: GridCellCoordinates | null, + customState: GridCustomStrategyState, duplicating: boolean, -): { commands: CanvasCommand[]; targetGridCell: GridCellCoordinates | null } { - let commands: CanvasCommand[] = [] - +): { + commands: CanvasCommand[] + targetCell: GridCellCoordinates | null + draggingFromCell: GridCellCoordinates | null + rootCell: GridCellCoordinates | null +} { if (interactionData.drag == null) { - return { commands: [], targetGridCell: null } + return { + commands: [], + targetCell: null, + rootCell: null, + draggingFromCell: null, + } } const mouseWindowPoint = canvasPointToWindowPoint( @@ -92,16 +102,14 @@ export function runGridRearrangeMove( canvasOffset, ) - let newTargetGridCell = targetGridCell ?? null - const cellUnderMouse = duplicating - ? getGridCellUnderMouseRecursive(mouseWindowPoint) - : getGridCellUnderMouse(mouseWindowPoint) - if (cellUnderMouse != null) { - newTargetGridCell = cellUnderMouse.coordinates - } - - if (newTargetGridCell == null || newTargetGridCell.row < 1 || newTargetGridCell.column < 1) { - return { commands: [], targetGridCell: null } + const newTargetCell = getTargetCell(customState.targetCell, duplicating, mouseWindowPoint) + if (newTargetCell == null) { + return { + commands: [], + targetCell: null, + draggingFromCell: null, + rootCell: null, + } } const originalElementMetadata = MetadataUtils.findElementByElementPath( @@ -109,46 +117,102 @@ export function runGridRearrangeMove( selectedElement, ) if (originalElementMetadata == null) { - return { commands: [], targetGridCell: null } + return { + commands: [], + targetCell: null, + rootCell: null, + draggingFromCell: null, + } + } + + const gridProperties = getElementGridProperties(originalElementMetadata) + + // calculate the difference between the cell the mouse started the interaction from, and the "root" + // cell of the element, meaning the top-left-most cell the element occupies. + const draggingFromCell = customState.draggingFromCell ?? newTargetCell + const rootCell = + customState.rootCell ?? gridCellCoordinates(gridProperties.row, gridProperties.column) + const coordsDiff = getCellCoordsDelta(draggingFromCell, rootCell) + + // get the new adjusted row + const row = getCoordBounds(newTargetCell, 'row', gridProperties.width, coordsDiff.row) + // get the new adjusted column + const column = getCoordBounds(newTargetCell, 'column', gridProperties.height, coordsDiff.column) + + return { + commands: [ + setProperty('always', targetElement, create('style', 'gridColumnStart'), column.start), + setProperty('always', targetElement, create('style', 'gridColumnEnd'), column.end), + setProperty('always', targetElement, create('style', 'gridRowStart'), row.start), + setProperty('always', targetElement, create('style', 'gridRowEnd'), row.end), + ], + targetCell: newTargetCell, + rootCell: rootCell, + draggingFromCell: draggingFromCell, } +} +function getTargetCell( + previousTargetCell: GridCellCoordinates | null, + duplicating: boolean, + mouseWindowPoint: WindowPoint, +): GridCellCoordinates | null { + let cell = previousTargetCell ?? null + const cellUnderMouse = duplicating + ? getGridCellUnderMouseRecursive(mouseWindowPoint) + : getGridCellUnderMouse(mouseWindowPoint) + if (cellUnderMouse != null) { + cell = cellUnderMouse.coordinates + } + if (cell == null || cell.row < 1 || cell.column < 1) { + return null + } + return cell +} + +function getElementGridProperties(element: ElementInstanceMetadata): { + row: number + width: number + column: number + height: number +} { + // get the grid fixtures (start and end for column and row) from the element metadata function getGridProperty(field: keyof GridElementProperties, fallback: number) { - const propValue = originalElementMetadata?.specialSizeMeasurements.elementGridProperties[field] + const propValue = element.specialSizeMeasurements.elementGridProperties[field] return propValue == null || propValue === 'auto' ? 0 : propValue.numericalPosition ?? fallback } - - const gridColumnStart = getGridProperty('gridColumnStart', 0) - const gridColumnEnd = getGridProperty('gridColumnEnd', 1) - const gridRowStart = getGridProperty('gridRowStart', 0) - const gridRowEnd = getGridProperty('gridRowEnd', 1) - - commands.push( - setProperty( - 'always', - targetElement, - create('style', 'gridColumnStart'), - newTargetGridCell.column, - ), - setProperty( - 'always', - targetElement, - create('style', 'gridColumnEnd'), - Math.max( - newTargetGridCell.column, - newTargetGridCell.column + (gridColumnEnd - gridColumnStart), - ), - ), - setProperty('always', targetElement, create('style', 'gridRowStart'), newTargetGridCell.row), - setProperty( - 'always', - targetElement, - create('style', 'gridRowEnd'), - Math.max(newTargetGridCell.row, newTargetGridCell.row + (gridRowEnd - gridRowStart)), - ), - ) + const column = getGridProperty('gridColumnStart', 0) + const height = getGridProperty('gridColumnEnd', 1) - column + const row = getGridProperty('gridRowStart', 0) + const width = getGridProperty('gridRowEnd', 1) - row return { - commands: commands, - targetGridCell: newTargetGridCell, + row, + width, + column, + height, } } + +function getCellCoordsDelta( + dragFrom: GridCellCoordinates, + rootCell: GridCellCoordinates, +): GridCellCoordinates { + const rowDiff = dragFrom.row - rootCell.row + const columnDiff = dragFrom.column - rootCell.column + + return gridCellCoordinates(rowDiff, columnDiff) +} + +function getCoordBounds( + cell: GridCellCoordinates, + coord: 'column' | 'row', + size: number, // width or height + adjustOffset: number, // adjustment based on the difference between the initial dragging cell and the root cell +): { start: number; end: number } { + // the start is the first cell's coord the element will occupy + const start = Math.max(1, cell[coord] - adjustOffset) + // the end is the last cell's coord the element will occupy + const end = Math.max(1, start + size) + return { start, end } +} diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-rearrange-move-duplicate-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-rearrange-move-duplicate-strategy.ts index ac9d686fd8e9..03e3f481dccd 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-rearrange-move-duplicate-strategy.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-rearrange-move-duplicate-strategy.ts @@ -85,14 +85,19 @@ export const gridRearrangeMoveDuplicateStrategy: CanvasStrategyFactory = ( const targetElement = EP.appendToPath(EP.parentPath(selectedElement), newUid) - const { commands: moveCommands, targetGridCell: newTargetGridCell } = runGridRearrangeMove( + const { + commands: moveCommands, + targetCell: targetGridCell, + draggingFromCell, + rootCell, + } = runGridRearrangeMove( targetElement, selectedElement, canvasState.startingMetadata, interactionSession.interactionData, canvasState.scale, canvasState.canvasOffset, - customState.targetGridCell, + customState.grid, true, ) if (moveCommands.length === 0) { @@ -109,7 +114,11 @@ export const gridRearrangeMoveDuplicateStrategy: CanvasStrategyFactory = ( setCursorCommand(CSSCursor.Duplicate), ], { - targetGridCell: newTargetGridCell, + grid: { + targetCell: targetGridCell, + draggingFromCell: draggingFromCell, + rootCell: rootCell, + }, duplicatedElementNewUids: duplicatedElementNewUids, }, ) diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-rearrange-move-strategy.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/grid-rearrange-move-strategy.spec.browser2.tsx index 6e47a9c9d13c..f17329a7796d 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-rearrange-move-strategy.spec.browser2.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-rearrange-move-strategy.spec.browser2.tsx @@ -18,14 +18,10 @@ describe('grid rearrange move strategy', () => { await mouseDragFromPointToPoint( sourceGridCell, - getRectCenter( - localRectangle({ - x: sourceGridCell.getBoundingClientRect().x, - y: sourceGridCell.getBoundingClientRect().y, - width: sourceGridCell.getBoundingClientRect().width, - height: sourceGridCell.getBoundingClientRect().height, - }), - ), + { + x: sourceGridCell.getBoundingClientRect().x + 10, + y: sourceGridCell.getBoundingClientRect().y + 10, + }, getRectCenter( localRectangle({ x: targetGridCell.getBoundingClientRect().x, diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-rearrange-move-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-rearrange-move-strategy.ts index fcd6eb2be2b8..fe18b99906a5 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-rearrange-move-strategy.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-rearrange-move-strategy.ts @@ -69,14 +69,19 @@ export const gridRearrangeMoveStrategy: CanvasStrategyFactory = ( const targetElement = selectedElement - const { commands: moveCommands, targetGridCell: newTargetGridCell } = runGridRearrangeMove( + const { + commands: moveCommands, + targetCell: targetGridCell, + draggingFromCell, + rootCell, + } = runGridRearrangeMove( targetElement, selectedElement, canvasState.startingMetadata, interactionSession.interactionData, canvasState.scale, canvasState.canvasOffset, - customState.targetGridCell, + customState.grid, false, ) if (moveCommands.length === 0) { @@ -84,7 +89,11 @@ export const gridRearrangeMoveStrategy: CanvasStrategyFactory = ( } return strategyApplicationResult(moveCommands, { - targetGridCell: newTargetGridCell, + grid: { + targetCell: targetGridCell, + draggingFromCell: draggingFromCell, + rootCell: rootCell, + }, }) }, } diff --git a/editor/src/components/editor/store/dispatch-strategies.spec.tsx b/editor/src/components/editor/store/dispatch-strategies.spec.tsx index b8c70e4e8a7f..d37679f21550 100644 --- a/editor/src/components/editor/store/dispatch-strategies.spec.tsx +++ b/editor/src/components/editor/store/dispatch-strategies.spec.tsx @@ -196,9 +196,13 @@ describe('interactionStart', () => { "duplicatedElementNewUids": Object {}, "elementsToRerender": Array [], "escapeHatchActivated": false, + "grid": Object { + "draggingFromCell": null, + "rootCell": null, + "targetCell": null, + }, "lastReorderIdx": null, "strategyGeneratedUidsCache": Object {}, - "targetGridCell": null, }, "sortedApplicableStrategies": null, "startingAllElementProps": Object {}, @@ -259,9 +263,13 @@ describe('interactionStart', () => { "duplicatedElementNewUids": Object {}, "elementsToRerender": Array [], "escapeHatchActivated": false, + "grid": Object { + "draggingFromCell": null, + "rootCell": null, + "targetCell": null, + }, "lastReorderIdx": null, "strategyGeneratedUidsCache": Object {}, - "targetGridCell": null, }, "sortedApplicableStrategies": null, "startingAllElementProps": Object {}, @@ -342,9 +350,13 @@ describe('interactionUpdate', () => { "duplicatedElementNewUids": Object {}, "elementsToRerender": Array [], "escapeHatchActivated": false, + "grid": Object { + "draggingFromCell": null, + "rootCell": null, + "targetCell": null, + }, "lastReorderIdx": null, "strategyGeneratedUidsCache": Object {}, - "targetGridCell": null, }, "sortedApplicableStrategies": Array [ Object { @@ -425,9 +437,13 @@ describe('interactionUpdate', () => { "duplicatedElementNewUids": Object {}, "elementsToRerender": Array [], "escapeHatchActivated": false, + "grid": Object { + "draggingFromCell": null, + "rootCell": null, + "targetCell": null, + }, "lastReorderIdx": null, "strategyGeneratedUidsCache": Object {}, - "targetGridCell": null, }, "sortedApplicableStrategies": null, "startingAllElementProps": Object {}, @@ -502,9 +518,13 @@ describe('interactionHardReset', () => { "duplicatedElementNewUids": Object {}, "elementsToRerender": Array [], "escapeHatchActivated": false, + "grid": Object { + "draggingFromCell": null, + "rootCell": null, + "targetCell": null, + }, "lastReorderIdx": null, "strategyGeneratedUidsCache": Object {}, - "targetGridCell": null, }, "sortedApplicableStrategies": Array [ Object { @@ -587,9 +607,13 @@ describe('interactionHardReset', () => { "duplicatedElementNewUids": Object {}, "elementsToRerender": Array [], "escapeHatchActivated": false, + "grid": Object { + "draggingFromCell": null, + "rootCell": null, + "targetCell": null, + }, "lastReorderIdx": null, "strategyGeneratedUidsCache": Object {}, - "targetGridCell": null, }, "sortedApplicableStrategies": null, "startingAllElementProps": Object {}, @@ -678,9 +702,13 @@ describe('interactionUpdate with user changed strategy', () => { "duplicatedElementNewUids": Object {}, "elementsToRerender": Array [], "escapeHatchActivated": false, + "grid": Object { + "draggingFromCell": null, + "rootCell": null, + "targetCell": null, + }, "lastReorderIdx": null, "strategyGeneratedUidsCache": Object {}, - "targetGridCell": null, }, "sortedApplicableStrategies": Array [ Object { @@ -764,9 +792,13 @@ describe('interactionUpdate with user changed strategy', () => { "duplicatedElementNewUids": Object {}, "elementsToRerender": Array [], "escapeHatchActivated": false, + "grid": Object { + "draggingFromCell": null, + "rootCell": null, + "targetCell": null, + }, "lastReorderIdx": null, "strategyGeneratedUidsCache": Object {}, - "targetGridCell": null, }, "sortedApplicableStrategies": null, "startingAllElementProps": Object {},