diff --git a/editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx b/editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx index c14f07eb91c0..aae5d84554ef 100644 --- a/editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx +++ b/editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx @@ -1,5 +1,4 @@ import React from 'react' -import { createSelector } from 'reselect' import { mapDropNulls, pushUniquelyBy, sortBy } from '../../../core/shared/array-utils' import type { ElementInstanceMetadataMap } from '../../../core/shared/element-template' import { arrayEqualsByReference, assertNever } from '../../../core/shared/utils' @@ -68,11 +67,10 @@ import type { InsertionSubject, InsertionSubjectWrapper } from '../../editor/edi import { generateUidWithExistingComponents } from '../../../core/model/element-template-utils' import { retargetStrategyToChildrenOfFragmentLikeElements } from './strategies/fragment-like-helpers' import { MetadataUtils } from '../../../core/model/element-metadata-utils' -import { gridRearrangeMoveStrategy } from './strategies/grid-rearrange-move-strategy' +import { gridMoveRearrangeStrategy } from './strategies/grid-move-rearrange-strategy' import { resizeGridStrategy } from './strategies/resize-grid-strategy' -import { rearrangeGridSwapStrategy } from './strategies/rearrange-grid-swap-strategy' import { gridResizeElementStrategy } from './strategies/grid-resize-element-strategy' -import { gridRearrangeMoveDuplicateStrategy } from './strategies/grid-rearrange-move-duplicate-strategy' +import { gridMoveRearrangeDuplicateStrategy } from './strategies/grid-move-rearrange-duplicate-strategy' import { setGridGapStrategy } from './strategies/set-grid-gap-strategy' import type { CanvasCommand } from '../commands/commands' import { foldAndApplyCommandsInner } from '../commands/commands' @@ -89,6 +87,8 @@ import { GridControls, isGridControlsProps, } from '../controls/grid-controls-for-strategies' +import { gridMoveReorderStrategy } from './strategies/grid-move-reorder-strategy' +import { gridMoveAbsoluteStrategy } from './strategies/grid-move-absolute' export type CanvasStrategyFactory = ( canvasState: InteractionCanvasState, @@ -116,10 +116,11 @@ const moveOrReorderStrategies: MetaCanvasStrategy = ( convertToAbsoluteAndMoveStrategy, convertToAbsoluteAndMoveAndSetParentFixedStrategy, reorderSliderStategy, - gridRearrangeMoveStrategy, - rearrangeGridSwapStrategy, - gridRearrangeMoveDuplicateStrategy, + gridMoveRearrangeStrategy, + gridMoveRearrangeDuplicateStrategy, + gridMoveReorderStrategy, gridRearrangeResizeKeyboardStrategy, + gridMoveAbsoluteStrategy, ], ) } 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 77a6260c8856..c889849c6d79 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.ts @@ -1,39 +1,39 @@ import type { ElementPath } from 'utopia-shared/src/types' 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 { ElementInstanceMetadataMap, + GridAutoOrTemplateBase, GridPositionValue, + SpecialSizeMeasurements, } from '../../../../core/shared/element-template' import { - gridPositionValue, type ElementInstanceMetadata, type GridContainerProperties, type GridElementProperties, type GridPosition, } from '../../../../core/shared/element-template' import type { CanvasRectangle } from '../../../../core/shared/math-utils' -import { - canvasPoint, - canvasVector, - isInfinityRectangle, - offsetPoint, -} from '../../../../core/shared/math-utils' import * as PP from '../../../../core/shared/property-path' -import { absolute } from '../../../../utils/utils' +import { assertNever } from '../../../../core/shared/utils' import type { GridDimension } from '../../../inspector/common/css-utils' import { - cssNumber, gridCSSRepeat, isCSSKeyword, isGridCSSRepeat, isStaticGridRepeat, + printGridAutoOrTemplateBase, } from '../../../inspector/common/css-utils' import type { CanvasCommand } from '../../commands/commands' import { deleteProperties } from '../../commands/delete-properties-command' -import { reorderElement } from '../../commands/reorder-element-command' -import { setCssLengthProperty } from '../../commands/set-css-length-command' -import { setProperty } from '../../commands/set-property-command' +import type { PropertyToUpdate } from '../../commands/set-property-command' +import { + propertyToDelete, + propertyToSet, + setProperty, + updateBulkProperties, +} from '../../commands/set-property-command' import type { DragInteractionData } from '../interaction-state' import type { GridCellCoordinates } from './grid-cell-bounds' import { @@ -41,174 +41,6 @@ import { getGridChildCellCoordBoundsFromCanvas, gridCellCoordinates, } from './grid-cell-bounds' -import { mapDropNulls } from '../../../../core/shared/array-utils' -import { assertNever } from '../../../../core/shared/utils' -import { showGridControls } from '../../commands/show-grid-controls-command' - -export function runGridRearrangeMove( - targetElement: ElementPath, - selectedElement: ElementPath, - jsxMetadata: ElementInstanceMetadataMap, - interactionData: DragInteractionData, - gridCellGlobalFrames: GridCellGlobalFrames, - gridTemplate: GridContainerProperties, - newPathAfterReparent?: ElementPath, -): CanvasCommand[] { - if (interactionData.drag == null) { - return [] - } - - const originalElement = MetadataUtils.findElementByElementPath(jsxMetadata, selectedElement) - if (originalElement == null) { - return [] - } - - const isReparent = newPathAfterReparent != null - - const mousePos = offsetPoint(interactionData.dragStart, interactionData.drag) - const targetCellData = getClosestGridCellToPoint(gridCellGlobalFrames, mousePos) - const targetCellCoords = targetCellData?.gridCellCoordinates - if (targetCellCoords == null) { - return [] - } - - const originalElementGridConfiguration = isReparent - ? { - originalCellBounds: { width: 1, height: 1 }, //when reparenting, we just put it in a single cell - mouseCellPosInOriginalElement: { row: 0, column: 0 }, - } - : getOriginalElementGridConfiguration(gridCellGlobalFrames, interactionData, originalElement) - if (originalElementGridConfiguration == null) { - return [] - } - - const { originalCellBounds, mouseCellPosInOriginalElement } = originalElementGridConfiguration - - // get the new adjusted row - const row = Math.max(targetCellCoords.row - mouseCellPosInOriginalElement.row, 1) - // get the new adjusted column - const column = Math.max(targetCellCoords.column - mouseCellPosInOriginalElement.column, 1) - - const pathForCommands = newPathAfterReparent ?? targetElement // when reparenting, we want to use the new path for commands - const gridPath = EP.parentPath(pathForCommands) - - const gridCellMoveCommands = setGridPropsCommands(pathForCommands, gridTemplate, { - gridColumnStart: gridPositionValue(column), - gridColumnEnd: gridPositionValue(column + originalCellBounds.height), - gridRowStart: gridPositionValue(row), - gridRowEnd: gridPositionValue(row + originalCellBounds.width), - }) - - const gridTemplateColumns = - gridTemplate.gridTemplateColumns?.type === 'DIMENSIONS' - ? gridTemplate.gridTemplateColumns.dimensions.length - : 1 - - // The "pure" index in the grid children for the cell under mouse - const possiblyReorderIndex = getGridPositionIndex({ - row: targetCellCoords.row, - column: targetCellCoords.column, - gridTemplateColumns: gridTemplateColumns, - }) - - // The siblings of the grid element being moved - const siblings = MetadataUtils.getChildrenUnordered(jsxMetadata, gridPath) - .filter((s) => !EP.pathsEqual(s.elementPath, selectedElement)) - .map( - (s, index): SortableGridElementProperties => ({ - ...s.specialSizeMeasurements.elementGridProperties, - index: index, - path: s.elementPath, - }), - ) - - // Sort the siblings and the cell under mouse ascending based on their grid coordinates, so that - // the indexes grow left-right, top-bottom. - const cellsSortedByPosition = siblings - .concat({ - ...{ - gridColumnStart: gridPositionValue(targetCellCoords.column), - gridColumnEnd: gridPositionValue(targetCellCoords.column), - gridRowStart: gridPositionValue(targetCellCoords.row), - gridRowEnd: gridPositionValue(targetCellCoords.row), - }, - path: selectedElement, - index: siblings.length + 1, - }) - .sort(sortElementsByGridPosition(gridTemplateColumns)) - - // If rearranging, reorder to the index based on the sorted cells arrays. - const indexInSortedCellsForRearrange = cellsSortedByPosition.findIndex((s) => - EP.pathsEqual(selectedElement, s.path), - ) - - const moveType = getGridMoveType({ - elementPath: targetElement, - originalElementMetadata: originalElement, - possiblyReorderIndex: possiblyReorderIndex, - cellsSortedByPosition: cellsSortedByPosition, - isReparent: isReparent, - }) - - const updateGridControlsCommand = showGridControls( - 'mid-interaction', - gridPath, - targetCellData?.gridCellCoordinates ?? null, - gridCellCoordinates(row, column), - ) - - switch (moveType) { - case 'absolute': { - if (isReparent) { - return [] - } - const absoluteMoveCommands = gridChildAbsoluteMoveCommands( - MetadataUtils.findElementByElementPath(jsxMetadata, targetElement), - MetadataUtils.getFrameOrZeroRectInCanvasCoords(gridPath, jsxMetadata), - interactionData, - ) - return [...absoluteMoveCommands, updateGridControlsCommand] - } - case 'rearrange': { - const targetRootCell = gridCellCoordinates(row, column) - const canvasRect = getGlobalFrameOfGridCell(gridCellGlobalFrames, targetRootCell) - const absoluteMoveCommands = - canvasRect == null || isReparent - ? [] - : gridChildAbsoluteMoveCommands( - MetadataUtils.findElementByElementPath(jsxMetadata, targetElement), - canvasRect, - interactionData, - ) - return [ - ...gridCellMoveCommands, - ...absoluteMoveCommands, - reorderElement( - 'always', - pathForCommands, - absolute(Math.max(indexInSortedCellsForRearrange, 0)), - ), - updateGridControlsCommand, - ] - } - case 'reorder': { - return [ - reorderElement('always', pathForCommands, absolute(possiblyReorderIndex)), - deleteProperties('always', pathForCommands, [ - PP.create('style', 'gridColumn'), - PP.create('style', 'gridRow'), - PP.create('style', 'gridColumnStart'), - PP.create('style', 'gridColumnEnd'), - PP.create('style', 'gridRowStart'), - PP.create('style', 'gridRowEnd'), - ]), - updateGridControlsCommand, - ] - } - default: - assertNever(moveType) - } -} export function gridPositionToValue(p: GridPosition | null | undefined): string | number | null { if (p == null) { @@ -365,62 +197,12 @@ function asMaybeNamedLineOrValue( return value } -function gridChildAbsoluteMoveCommands( - targetMetadata: ElementInstanceMetadata | null, - containingRect: CanvasRectangle, - dragInteractionData: DragInteractionData, -): CanvasCommand[] { - if ( - targetMetadata == null || - targetMetadata.globalFrame == null || - isInfinityRectangle(targetMetadata.globalFrame) || - !MetadataUtils.isPositionAbsolute(targetMetadata) - ) { - return [] - } - - const offsetInTarget = canvasPoint({ - x: dragInteractionData.originalDragStart.x - targetMetadata.globalFrame.x, - y: dragInteractionData.originalDragStart.y - targetMetadata.globalFrame.y, - }) - - const dragOffset = offsetPoint( - dragInteractionData.originalDragStart, - dragInteractionData.drag ?? canvasPoint({ x: 0, y: 0 }), - ) - - const offset = canvasVector({ - x: dragOffset.x - containingRect.x - offsetInTarget.x, - y: dragOffset.y - containingRect.y - offsetInTarget.y, - }) - - return [ - deleteProperties('always', targetMetadata.elementPath, [ - PP.create('style', 'top'), - PP.create('style', 'left'), - PP.create('style', 'right'), - PP.create('style', 'bottom'), - ]), - setCssLengthProperty( - 'always', - targetMetadata.elementPath, - PP.create('style', 'top'), - { type: 'EXPLICIT_CSS_NUMBER', value: cssNumber(offset.y, null) }, - null, - ), - setCssLengthProperty( - 'always', - targetMetadata.elementPath, - PP.create('style', 'left'), - { type: 'EXPLICIT_CSS_NUMBER', value: cssNumber(offset.x, null) }, - null, - ), - ] +export type SortableGridElementProperties = GridElementProperties & { + path: ElementPath + index: number } -type SortableGridElementProperties = GridElementProperties & { path: ElementPath; index: number } - -function sortElementsByGridPosition(gridTemplateColumns: number) { +export function sortElementsByGridPosition(gridTemplateColumns: number) { return function (a: SortableGridElementProperties, b: SortableGridElementProperties): number { function getPosition(index: number, e: GridElementProperties) { if ( @@ -442,80 +224,11 @@ function sortElementsByGridPosition(gridTemplateColumns: number) { } } -type GridMoveType = - | 'reorder' // reorder the element in the code based on the ascending position, and remove explicit positioning props - | 'rearrange' // set explicit positioning props, and reorder based on the visual location - | 'absolute' // a regular absolute move, relative to the grid - -function getGridMoveType(params: { - elementPath: ElementPath - originalElementMetadata: ElementInstanceMetadata | null - possiblyReorderIndex: number - cellsSortedByPosition: SortableGridElementProperties[] - isReparent: boolean -}): GridMoveType { - const specialSizeMeasurements = params.originalElementMetadata?.specialSizeMeasurements - if ( - !params.isReparent && - specialSizeMeasurements != null && - MetadataUtils.isPositionAbsolute(params.originalElementMetadata) - ) { - return MetadataUtils.hasNoGridCellPositioning(specialSizeMeasurements) - ? 'absolute' - : 'rearrange' - } - if (params.possiblyReorderIndex >= params.cellsSortedByPosition.length) { - return 'rearrange' - } - - const elementGridProperties = specialSizeMeasurements?.elementGridProperties - const gridRowStart = gridPositionNumberValue(elementGridProperties?.gridRowStart ?? null) - const gridColumnStart = gridPositionNumberValue(elementGridProperties?.gridColumnStart ?? null) - const gridRowEnd = gridPositionNumberValue(elementGridProperties?.gridRowEnd ?? null) - const gridColumnEnd = gridPositionNumberValue(elementGridProperties?.gridColumnEnd ?? null) - - const isMultiCellChild = - (gridRowEnd != null && gridRowStart != null && gridRowEnd > gridRowStart + 1) || - (gridColumnEnd != null && gridColumnStart != null && gridColumnEnd > gridColumnStart + 1) - - if (isMultiCellChild) { - return 'rearrange' - } - - // The first element is intrinsically in order, so try to adjust for that - if (params.possiblyReorderIndex === 0) { - const isTheOnlyChild = params.cellsSortedByPosition.length === 1 - const isAlreadyTheFirstChild = - EP.toUid(params.cellsSortedByPosition[0].path) === EP.toUid(params.elementPath) - - const isAlreadyAtOrigin = gridRowStart === 1 && gridColumnStart === 1 - - if (isTheOnlyChild || isAlreadyTheFirstChild || isAlreadyAtOrigin) { - return 'reorder' - } - } - - const previousElement = params.cellsSortedByPosition.at(params.possiblyReorderIndex - 1) - if (previousElement == null) { - return 'rearrange' - } - const previousElementColumn = previousElement.gridColumnStart ?? null - const previousElementRow = previousElement.gridRowStart ?? null - return isGridPositionNumericValue(previousElementColumn) && - isGridPositionNumericValue(previousElementRow) - ? 'rearrange' - : 'reorder' -} - function isGridPositionNumericValue(p: GridPosition | null): p is GridPositionValue { return p != null && !(isCSSKeyword(p) && p.value === 'auto') } -function gridPositionNumberValue(p: GridPosition | null): number | null { - return isGridPositionNumericValue(p) ? p.numericalPosition : null -} - -function getGridPositionIndex(props: { +export function getGridPositionIndex(props: { row: number column: number gridTemplateColumns: number @@ -718,7 +431,7 @@ export function getGridRelatedIndexes(params: { return expandedRelatedIndexes[params.index] ?? [] } -function getOriginalElementGridConfiguration( +export function getOriginalElementGridConfiguration( gridCellGlobalFrames: GridCellGlobalFrames, interactionData: DragInteractionData, originalElement: ElementInstanceMetadata, @@ -788,3 +501,131 @@ export function isJustAutoGridDimension(dimensions: GridDimension[]): boolean { dimensions[0].value.value === 'auto' ) } + +type GridElementPinState = 'not-pinned' | 'auto-pinned' | 'pinned' + +export function getGridElementPinState( + elementGridPropertiesFromProps: GridElementProperties | null, +): GridElementPinState { + if ( + elementGridPropertiesFromProps?.gridColumnEnd == null || + elementGridPropertiesFromProps?.gridColumnStart == null || + elementGridPropertiesFromProps?.gridRowEnd == null || + elementGridPropertiesFromProps?.gridRowStart == null + ) { + return 'not-pinned' + } + if ( + isGridPositionNumericValue(elementGridPropertiesFromProps?.gridColumnEnd ?? null) || + isGridPositionNumericValue(elementGridPropertiesFromProps?.gridColumnStart ?? null) || + isGridPositionNumericValue(elementGridPropertiesFromProps?.gridRowEnd ?? null) || + isGridPositionNumericValue(elementGridPropertiesFromProps?.gridRowStart ?? null) + ) { + return 'pinned' + } + return 'auto-pinned' +} + +export function isFlowGridChild(child: ElementInstanceMetadata) { + return ( + getGridElementPinState(child.specialSizeMeasurements.elementGridPropertiesFromProps) !== + 'pinned' + ) +} + +function restoreGridTemplateFromProps(params: { + columns: GridAutoOrTemplateBase + rows: GridAutoOrTemplateBase +}): PropertyToUpdate[] { + let properties: PropertyToUpdate[] = [] + const newCols = printGridAutoOrTemplateBase(params.columns) + const newRows = printGridAutoOrTemplateBase(params.rows) + if (newCols === '') { + properties.push(propertyToDelete(PP.create('style', 'gridTemplateColumns'))) + } else { + properties.push(propertyToSet(PP.create('style', 'gridTemplateColumns'), newCols)) + } + if (newRows === '') { + properties.push(propertyToDelete(PP.create('style', 'gridTemplateRows'))) + } else { + properties.push(propertyToSet(PP.create('style', 'gridTemplateRows'), newRows)) + } + return properties +} + +type GridInitialTemplates = { + calculated: { + columns: GridAutoOrTemplateBase + rows: GridAutoOrTemplateBase + } + fromProps: { + columns: GridAutoOrTemplateBase + rows: GridAutoOrTemplateBase + } +} + +export function getParentGridTemplatesFromChildMeasurements( + specialSizeMeasurements: SpecialSizeMeasurements, +): GridInitialTemplates | null { + const parentTemplateCalculated = specialSizeMeasurements.parentContainerGridProperties + const parentTemplateFromProps = specialSizeMeasurements.parentContainerGridPropertiesFromProps + + const templateColsCalculated = parentTemplateCalculated.gridTemplateColumns + if (templateColsCalculated == null) { + return null + } + const templateRowsCalculated = parentTemplateCalculated.gridTemplateRows + if (templateRowsCalculated == null) { + return null + } + + const templateColsFromProps = parentTemplateFromProps.gridTemplateColumns + if (templateColsFromProps == null) { + return null + } + const templateRowsFromProps = parentTemplateFromProps.gridTemplateRows + if (templateRowsFromProps == null) { + return null + } + + return { + calculated: { + columns: templateColsCalculated, + rows: templateRowsCalculated, + }, + fromProps: { + columns: templateColsFromProps, + rows: templateRowsFromProps, + }, + } +} + +export function gridMoveStrategiesExtraCommands( + parentGridPath: ElementPath, + initialTemplates: GridInitialTemplates, +) { + const midInteractionCommands = [ + // during the interaction, freeze the template with the calculated values… + updateBulkProperties('mid-interaction', parentGridPath, [ + propertyToSet( + PP.create('style', 'gridTemplateColumns'), + printGridAutoOrTemplateBase(initialTemplates.calculated.columns), + ), + propertyToSet( + PP.create('style', 'gridTemplateRows'), + printGridAutoOrTemplateBase(initialTemplates.calculated.rows), + ), + ]), + ] + + const onCompleteCommands = [ + // …eventually, restore the grid template on complete. + updateBulkProperties( + 'on-complete', + parentGridPath, + restoreGridTemplateFromProps(initialTemplates.fromProps), + ), + ] + + return { midInteractionCommands, onCompleteCommands } +} diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-move-absolute.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-move-absolute.ts new file mode 100644 index 000000000000..c81b4b2c9aff --- /dev/null +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-move-absolute.ts @@ -0,0 +1,287 @@ +import type { ElementPath } from 'utopia-shared/src/types' +import { MetadataUtils } from '../../../../core/model/element-metadata-utils' +import * as EP from '../../../../core/shared/element-path' +import { + type ElementInstanceMetadata, + type ElementInstanceMetadataMap, + type GridContainerProperties, +} from '../../../../core/shared/element-template' +import type { CanvasRectangle } from '../../../../core/shared/math-utils' +import { + canvasPoint, + canvasRectangle, + isInfinityRectangle, + offsetPoint, + pointDifference, + zeroRectangle, +} from '../../../../core/shared/math-utils' +import * as PP from '../../../../core/shared/property-path' +import { cssNumber } from '../../../inspector/common/css-utils' +import type { CanvasCommand } from '../../commands/commands' +import { deleteProperties } from '../../commands/delete-properties-command' +import { setCssLengthProperty } from '../../commands/set-css-length-command' +import { showGridControls } from '../../commands/show-grid-controls-command' +import { controlsForGridPlaceholders } from '../../controls/grid-controls-for-strategies' +import type { CanvasStrategyFactory } from '../canvas-strategies' +import { onlyFitWhenDraggingThisControl } from '../canvas-strategies' +import type { InteractionCanvasState } from '../canvas-strategy-types' +import { + emptyStrategyApplicationResult, + getTargetPathsFromInteractionTarget, + strategyApplicationResult, +} from '../canvas-strategy-types' +import type { DragInteractionData, InteractionSession } from '../interaction-state' +import type { GridCellGlobalFrames } from './grid-helpers' +import { + findOriginalGrid, + getGlobalFrameOfGridCell, + getOriginalElementGridConfiguration, + getParentGridTemplatesFromChildMeasurements, + gridMoveStrategiesExtraCommands, +} from './grid-helpers' +import { runGridMoveRearrange } from './grid-move-rearrange-strategy' +import { getTargetGridCellData } from '../../../inspector/grid-helpers' + +export const gridMoveAbsoluteStrategy: CanvasStrategyFactory = ( + canvasState: InteractionCanvasState, + interactionSession: InteractionSession | null, +) => { + const selectedElements = getTargetPathsFromInteractionTarget(canvasState.interactionTarget) + if ( + selectedElements.length !== 1 || + interactionSession == null || + interactionSession.interactionData.type !== 'DRAG' || + interactionSession.interactionData.drag == null || + interactionSession.activeControl.type !== 'GRID_CELL_HANDLE' || + interactionSession.interactionData.modifiers.alt + ) { + return null + } + + const selectedElement = selectedElements[0] + if (!MetadataUtils.isGridCell(canvasState.startingMetadata, selectedElement)) { + return null + } + + const selectedElementMetadata = MetadataUtils.findElementByElementPath( + canvasState.startingMetadata, + selectedElement, + ) + if (selectedElementMetadata == null) { + return null + } + const initialTemplates = getParentGridTemplatesFromChildMeasurements( + selectedElementMetadata.specialSizeMeasurements, + ) + if (initialTemplates == null) { + return null + } + + const parentGridPath = findOriginalGrid( + canvasState.startingMetadata, + EP.parentPath(selectedElement), + ) // TODO don't use EP.parentPath + if (parentGridPath == null) { + return null + } + + const gridFrame = MetadataUtils.findElementByElementPath( + canvasState.startingMetadata, + parentGridPath, + )?.globalFrame + if (gridFrame == null || isInfinityRectangle(gridFrame)) { + return null + } + + if (!MetadataUtils.isPositionAbsolute(selectedElementMetadata)) { + return null + } + + return { + id: 'absolute-grid-move-strategy', + name: 'Grid move (Abs)', + descriptiveLabel: 'Grid move (Abs)', + icon: { + category: 'tools', + type: 'pointer', + }, + controlsToRender: [controlsForGridPlaceholders(parentGridPath, 'visible-only-while-active')], + fitness: onlyFitWhenDraggingThisControl(interactionSession, 'GRID_CELL_HANDLE', 2), + apply: () => { + if ( + interactionSession == null || + interactionSession.interactionData.type !== 'DRAG' || + interactionSession.interactionData.drag == null || + interactionSession.activeControl.type !== 'GRID_CELL_HANDLE' + ) { + return emptyStrategyApplicationResult + } + + const { commands, elementsToRerender } = getCommandsAndPatchForGridAbsoluteMove( + canvasState, + interactionSession.interactionData, + selectedElement, + parentGridPath, + ) + if (commands.length === 0) { + return emptyStrategyApplicationResult + } + + const { midInteractionCommands, onCompleteCommands } = gridMoveStrategiesExtraCommands( + parentGridPath, + initialTemplates, + ) + return strategyApplicationResult( + [...midInteractionCommands, ...onCompleteCommands, ...commands], + elementsToRerender, + ) + }, + } +} + +function getCommandsAndPatchForGridAbsoluteMove( + canvasState: InteractionCanvasState, + interactionData: DragInteractionData, + selectedElement: ElementPath, + gridPath: ElementPath, +): { + commands: CanvasCommand[] + elementsToRerender: ElementPath[] +} { + if (interactionData.drag == null) { + return { commands: [], elementsToRerender: [] } + } + + const selectedElementMetadata = MetadataUtils.findElementByElementPath( + canvasState.startingMetadata, + selectedElement, + ) + if (selectedElementMetadata == null) { + return { commands: [], elementsToRerender: [] } + } + + const { parentGridCellGlobalFrames, parentContainerGridProperties } = + selectedElementMetadata.specialSizeMeasurements + if (parentGridCellGlobalFrames == null) { + return { commands: [], elementsToRerender: [] } + } + + const commands = runGridMoveAbsolute( + canvasState.startingMetadata, + interactionData, + selectedElementMetadata, + gridPath, + parentGridCellGlobalFrames, + parentContainerGridProperties, + ) + + return { + commands: commands, + elementsToRerender: [gridPath, selectedElement], + } +} + +function runGridMoveAbsolute( + jsxMetadata: ElementInstanceMetadataMap, + interactionData: DragInteractionData, + selectedElementMetadata: ElementInstanceMetadata, + gridPath: ElementPath, + gridCellGlobalFrames: GridCellGlobalFrames, + gridTemplate: GridContainerProperties, +): CanvasCommand[] { + if (interactionData.drag == null) { + return [] + } + + const gridConfig = getOriginalElementGridConfiguration( + gridCellGlobalFrames, + interactionData, + selectedElementMetadata, + ) + if (gridConfig == null) { + return [] + } + const { mouseCellPosInOriginalElement } = gridConfig + + const targetGridCellData = getTargetGridCellData( + interactionData, + gridCellGlobalFrames, + mouseCellPosInOriginalElement, + ) + if (targetGridCellData == null) { + return [] + } + const { targetCellCoords, targetRootCell } = targetGridCellData + + // if moving an absolutely-positioned child which does not have pinning + // props, do not set them at all. + if (MetadataUtils.hasNoGridCellPositioning(selectedElementMetadata.specialSizeMeasurements)) { + return [ + showGridControls('mid-interaction', gridPath, targetCellCoords, targetRootCell), + ...gridChildAbsoluteMoveCommands( + selectedElementMetadata, + MetadataUtils.getFrameOrZeroRectInCanvasCoords(gridPath, jsxMetadata), + interactionData, + ), + ] + } + + // otherwise, return a rearrange move + absolute adjustment + return [ + ...runGridMoveRearrange( + jsxMetadata, + interactionData, + selectedElementMetadata, + gridPath, + gridCellGlobalFrames, + gridTemplate, + null, + ), + ...gridChildAbsoluteMoveCommands( + selectedElementMetadata, + getGlobalFrameOfGridCell(gridCellGlobalFrames, targetRootCell) ?? + canvasRectangle(zeroRectangle), + interactionData, + ), + ] +} + +function gridChildAbsoluteMoveCommands( + element: ElementInstanceMetadata, + containingRect: CanvasRectangle, + dragInteractionData: DragInteractionData, +): CanvasCommand[] { + if ( + element.globalFrame == null || + isInfinityRectangle(element.globalFrame) || + dragInteractionData.drag == null + ) { + return [] + } + + const offsetInTarget = pointDifference(containingRect, element.globalFrame) + const dragOffset = offsetPoint(offsetInTarget, dragInteractionData.drag) + + return [ + deleteProperties('always', element.elementPath, [ + PP.create('style', 'top'), + PP.create('style', 'left'), + PP.create('style', 'right'), + PP.create('style', 'bottom'), + ]), + setCssLengthProperty( + 'always', + element.elementPath, + PP.create('style', 'top'), + { type: 'EXPLICIT_CSS_NUMBER', value: cssNumber(dragOffset.y, null) }, + null, + ), + setCssLengthProperty( + 'always', + element.elementPath, + PP.create('style', 'left'), + { type: 'EXPLICIT_CSS_NUMBER', value: cssNumber(dragOffset.x, null) }, + null, + ), + ] +} 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-move-rearrange-duplicate-strategy.ts similarity index 64% rename from editor/src/components/canvas/canvas-strategies/strategies/grid-rearrange-move-duplicate-strategy.ts rename to editor/src/components/canvas/canvas-strategies/strategies/grid-move-rearrange-duplicate-strategy.ts index 91d3e60875ee..b3623409513c 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-rearrange-move-duplicate-strategy.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-move-rearrange-duplicate-strategy.ts @@ -1,6 +1,7 @@ import { MetadataUtils } from '../../../../core/model/element-metadata-utils' import { generateUidWithExistingComponents } from '../../../../core/model/element-template-utils' import * as EP from '../../../../core/shared/element-path' +import { isInfinityRectangle } from '../../../../core/shared/math-utils' import { CSSCursor } from '../../../../uuiui-deps' import { duplicateElement } from '../../commands/duplicate-element-command' import { setCursorCommand } from '../../commands/set-cursor-command' @@ -12,14 +13,19 @@ import type { CanvasStrategyFactory } from '../canvas-strategies' import { onlyFitWhenDraggingThisControl } from '../canvas-strategies' import type { CustomStrategyState, InteractionCanvasState } from '../canvas-strategy-types' import { - getTargetPathsFromInteractionTarget, emptyStrategyApplicationResult, + getTargetPathsFromInteractionTarget, strategyApplicationResult, } from '../canvas-strategy-types' import type { InteractionSession } from '../interaction-state' -import { runGridRearrangeMove } from './grid-helpers' +import { + findOriginalGrid, + getParentGridTemplatesFromChildMeasurements, + gridMoveStrategiesExtraCommands, +} from './grid-helpers' +import { runGridMoveRearrange } from './grid-move-rearrange-strategy' -export const gridRearrangeMoveDuplicateStrategy: CanvasStrategyFactory = ( +export const gridMoveRearrangeDuplicateStrategy: CanvasStrategyFactory = ( canvasState: InteractionCanvasState, interactionSession: InteractionSession | null, customState: CustomStrategyState, @@ -41,15 +47,46 @@ export const gridRearrangeMoveDuplicateStrategy: CanvasStrategyFactory = ( return null } + const selectedElementMetadata = MetadataUtils.findElementByElementPath( + canvasState.startingMetadata, + selectedElement, + ) + if (selectedElementMetadata == null) { + return null + } + + const initialTemplates = getParentGridTemplatesFromChildMeasurements( + selectedElementMetadata.specialSizeMeasurements, + ) + if (initialTemplates == null) { + return null + } + + const parentGridPath = findOriginalGrid( + canvasState.startingMetadata, + EP.parentPath(selectedElement), + ) // TODO don't use EP.parentPath + if (parentGridPath == null) { + return null + } + + const gridFrame = MetadataUtils.findElementByElementPath( + canvasState.startingMetadata, + parentGridPath, + )?.globalFrame + if (gridFrame == null || isInfinityRectangle(gridFrame)) { + return null + } + return { id: 'rearrange-grid-move-duplicate-strategy', - name: 'Rearrange Grid (Duplicate)', - descriptiveLabel: 'Rearrange Grid (Duplicate)', + name: 'Grid Rearrange (Duplicate)', + descriptiveLabel: 'Grid Rearrange (Duplicate)', icon: { category: 'tools', type: 'pointer', }, - controlsToRender: [controlsForGridPlaceholders(EP.parentPath(selectedElement))], + controlsToRender: [controlsForGridPlaceholders(parentGridPath)], fitness: onlyFitWhenDraggingThisControl(interactionSession, 'GRID_CELL_HANDLE', 3), apply: () => { if ( @@ -70,38 +107,35 @@ export const gridRearrangeMoveDuplicateStrategy: CanvasStrategyFactory = ( duplicatedElementNewUids[oldUid] = newUid } - const targetElement = EP.appendToPath(EP.parentPath(selectedElement), newUid) - - const selectedElementMetadata = MetadataUtils.findElementByElementPath( - canvasState.startingMetadata, - selectedElement, - ) - if (selectedElementMetadata == null) { - return emptyStrategyApplicationResult - } + const targetElement = EP.appendToPath(parentGridPath, newUid) const { parentGridCellGlobalFrames, parentContainerGridProperties } = selectedElementMetadata.specialSizeMeasurements - - const moveCommands = - parentGridCellGlobalFrames != null - ? runGridRearrangeMove( - targetElement, - selectedElement, - canvasState.startingMetadata, - interactionSession.interactionData, - parentGridCellGlobalFrames, - parentContainerGridProperties, - ) - : [] - if (moveCommands.length === 0) { + if (parentGridCellGlobalFrames == null) { return emptyStrategyApplicationResult } + const moveCommands = runGridMoveRearrange( + canvasState.startingMetadata, + interactionSession.interactionData, + selectedElementMetadata, + parentGridPath, + parentGridCellGlobalFrames, + parentContainerGridProperties, + null, + ) + + const { midInteractionCommands, onCompleteCommands } = gridMoveStrategiesExtraCommands( + parentGridPath, + initialTemplates, + ) + return strategyApplicationResult( [ duplicateElement('always', selectedElement, newUid), ...moveCommands, + ...midInteractionCommands, + ...onCompleteCommands, updateSelectedViews('always', [targetElement]), updateHighlightedViews('always', [targetElement]), setCursorCommand(CSSCursor.Duplicate), diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-move-rearrange-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-move-rearrange-strategy.ts new file mode 100644 index 000000000000..1d54d077cffb --- /dev/null +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-move-rearrange-strategy.ts @@ -0,0 +1,274 @@ +import type { ElementPath } from 'utopia-shared/src/types' +import { MetadataUtils } from '../../../../core/model/element-metadata-utils' +import * as EP from '../../../../core/shared/element-path' +import type { + ElementInstanceMetadata, + ElementInstanceMetadataMap, + GridContainerProperties, +} from '../../../../core/shared/element-template' +import { gridPositionValue } from '../../../../core/shared/element-template' +import { isInfinityRectangle } from '../../../../core/shared/math-utils' +import { absolute } from '../../../../utils/utils' +import type { CanvasCommand } from '../../commands/commands' +import { reorderElement } from '../../commands/reorder-element-command' +import { showGridControls } from '../../commands/show-grid-controls-command' +import { controlsForGridPlaceholders } from '../../controls/grid-controls-for-strategies' +import type { CanvasStrategyFactory } from '../canvas-strategies' +import { onlyFitWhenDraggingThisControl } from '../canvas-strategies' +import type { InteractionCanvasState } from '../canvas-strategy-types' +import { + emptyStrategyApplicationResult, + getTargetPathsFromInteractionTarget, + strategyApplicationResult, +} from '../canvas-strategy-types' +import type { DragInteractionData, InteractionSession } from '../interaction-state' +import type { GridCellGlobalFrames, SortableGridElementProperties } from './grid-helpers' +import { + findOriginalGrid, + getOriginalElementGridConfiguration, + getParentGridTemplatesFromChildMeasurements, + gridMoveStrategiesExtraCommands, + setGridPropsCommands, + sortElementsByGridPosition, +} from './grid-helpers' +import { getTargetGridCellData } from '../../../inspector/grid-helpers' + +export const gridMoveRearrangeStrategy: CanvasStrategyFactory = ( + canvasState: InteractionCanvasState, + interactionSession: InteractionSession | null, +) => { + const selectedElements = getTargetPathsFromInteractionTarget(canvasState.interactionTarget) + if ( + selectedElements.length !== 1 || + interactionSession == null || + interactionSession.interactionData.type !== 'DRAG' || + interactionSession.interactionData.drag == null || + interactionSession.activeControl.type !== 'GRID_CELL_HANDLE' || + interactionSession.interactionData.modifiers.alt + ) { + return null + } + + const selectedElement = selectedElements[0] + if (!MetadataUtils.isGridCell(canvasState.startingMetadata, selectedElement)) { + return null + } + + const selectedElementMetadata = MetadataUtils.findElementByElementPath( + canvasState.startingMetadata, + selectedElement, + ) + if (selectedElementMetadata == null) { + return null + } + const initialTemplates = getParentGridTemplatesFromChildMeasurements( + selectedElementMetadata.specialSizeMeasurements, + ) + if (initialTemplates == null) { + return null + } + + const parentGridPath = findOriginalGrid( + canvasState.startingMetadata, + EP.parentPath(selectedElement), + ) // TODO don't use EP.parentPath + if (parentGridPath == null) { + return null + } + + const gridFrame = MetadataUtils.findElementByElementPath( + canvasState.startingMetadata, + parentGridPath, + )?.globalFrame + if (gridFrame == null || isInfinityRectangle(gridFrame)) { + return null + } + + if (MetadataUtils.isPositionAbsolute(selectedElementMetadata)) { + return null + } + + return { + id: 'rearrange-grid-move-strategy', + name: 'Grid rearrange', + descriptiveLabel: 'Grid rearrange', + icon: { + category: 'tools', + type: 'pointer', + }, + controlsToRender: [controlsForGridPlaceholders(parentGridPath, 'visible-only-while-active')], + fitness: onlyFitWhenDraggingThisControl(interactionSession, 'GRID_CELL_HANDLE', 2), + apply: () => { + if ( + interactionSession == null || + interactionSession.interactionData.type !== 'DRAG' || + interactionSession.interactionData.drag == null || + interactionSession.activeControl.type !== 'GRID_CELL_HANDLE' + ) { + return emptyStrategyApplicationResult + } + + const { commands, elementsToRerender } = getCommandsAndPatchForGridRearrange( + canvasState, + interactionSession.interactionData, + selectedElement, + parentGridPath, + ) + if (commands.length === 0) { + return emptyStrategyApplicationResult + } + + const { midInteractionCommands, onCompleteCommands } = gridMoveStrategiesExtraCommands( + parentGridPath, + initialTemplates, + ) + return strategyApplicationResult( + [...midInteractionCommands, ...onCompleteCommands, ...commands], + elementsToRerender, + ) + }, + } +} + +function getCommandsAndPatchForGridRearrange( + canvasState: InteractionCanvasState, + interactionData: DragInteractionData, + selectedElement: ElementPath, + gridPath: ElementPath, +): { + commands: CanvasCommand[] + elementsToRerender: ElementPath[] +} { + if (interactionData.drag == null) { + return { commands: [], elementsToRerender: [] } + } + + const selectedElementMetadata = MetadataUtils.findElementByElementPath( + canvasState.startingMetadata, + selectedElement, + ) + if (selectedElementMetadata == null) { + return { commands: [], elementsToRerender: [] } + } + + const { parentGridCellGlobalFrames, parentContainerGridProperties } = + selectedElementMetadata.specialSizeMeasurements + if (parentGridCellGlobalFrames == null) { + return { commands: [], elementsToRerender: [] } + } + + const commands = runGridMoveRearrange( + canvasState.startingMetadata, + interactionData, + selectedElementMetadata, + gridPath, + parentGridCellGlobalFrames, + parentContainerGridProperties, + null, + ) + + return { + commands: commands, + elementsToRerender: [gridPath, selectedElement], + } +} + +export function runGridMoveRearrange( + jsxMetadata: ElementInstanceMetadataMap, + interactionData: DragInteractionData, + selectedElementMetadata: ElementInstanceMetadata, + gridPath: ElementPath, + gridCellGlobalFrames: GridCellGlobalFrames, + gridTemplate: GridContainerProperties, + newPathAfterReparent: ElementPath | null, +): CanvasCommand[] { + if (interactionData.drag == null) { + return [] + } + + const isReparent = newPathAfterReparent != null + const pathForCommands = isReparent ? newPathAfterReparent : selectedElementMetadata.elementPath // when reparenting, we want to use the new path for commands + + const gridConfig = isReparent + ? { + originalCellBounds: { width: 1, height: 1 }, // when reparenting, we just put it in a single cell + mouseCellPosInOriginalElement: { row: 0, column: 0 }, + } + : getOriginalElementGridConfiguration( + gridCellGlobalFrames, + interactionData, + selectedElementMetadata, + ) + if (gridConfig == null) { + return [] + } + const { mouseCellPosInOriginalElement, originalCellBounds } = gridConfig + + const targetGridCellData = getTargetGridCellData( + interactionData, + gridCellGlobalFrames, + mouseCellPosInOriginalElement, + ) + if (targetGridCellData == null) { + return [] + } + const { targetCellCoords, targetRootCell } = targetGridCellData + + const gridCellMoveCommands = setGridPropsCommands(pathForCommands, gridTemplate, { + gridColumnStart: gridPositionValue(targetRootCell.column), + gridColumnEnd: gridPositionValue(targetRootCell.column + originalCellBounds.height), + gridRowStart: gridPositionValue(targetRootCell.row), + gridRowEnd: gridPositionValue(targetRootCell.row + originalCellBounds.width), + }) + + // The siblings of the grid element being moved + const siblings = MetadataUtils.getChildrenUnordered(jsxMetadata, gridPath) + .filter((s) => !EP.pathsEqual(s.elementPath, selectedElementMetadata.elementPath)) + .map( + (s, index): SortableGridElementProperties => ({ + ...s.specialSizeMeasurements.elementGridProperties, + index: index, + path: s.elementPath, + }), + ) + + // Sort the siblings and the cell under mouse ascending based on their grid coordinates, so that + // the indexes grow left-right, top-bottom. + const templateColumnsCount = + gridTemplate.gridTemplateColumns?.type === 'DIMENSIONS' + ? gridTemplate.gridTemplateColumns.dimensions.length + : 1 + const cellsSortedByPosition = siblings + .concat({ + ...{ + gridColumnStart: gridPositionValue(targetCellCoords.column), + gridColumnEnd: gridPositionValue(targetCellCoords.column), + gridRowStart: gridPositionValue(targetCellCoords.row), + gridRowEnd: gridPositionValue(targetCellCoords.row), + }, + path: selectedElementMetadata.elementPath, + index: siblings.length + 1, + }) + .sort(sortElementsByGridPosition(templateColumnsCount)) + + const indexInSortedCellsForRearrange = cellsSortedByPosition.findIndex((s) => + EP.pathsEqual(selectedElementMetadata.elementPath, s.path), + ) + + const updateGridControlsCommand = showGridControls( + 'mid-interaction', + gridPath, + targetCellCoords, + targetRootCell, + ) + + return [ + ...gridCellMoveCommands, + reorderElement( + 'always', + pathForCommands, + absolute(Math.max(indexInSortedCellsForRearrange, 0)), + ), + updateGridControlsCommand, + ] +} diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-move-reorder-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-move-reorder-strategy.ts new file mode 100644 index 000000000000..e603ba364f9f --- /dev/null +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-move-reorder-strategy.ts @@ -0,0 +1,256 @@ +import type { ElementPath } from 'utopia-shared/src/types' +import { MetadataUtils } from '../../../../core/model/element-metadata-utils' +import * as EP from '../../../../core/shared/element-path' +import type { + ElementInstanceMetadata, + ElementInstanceMetadataMap, + GridContainerProperties, +} from '../../../../core/shared/element-template' +import { isInfinityRectangle } from '../../../../core/shared/math-utils' +import * as PP from '../../../../core/shared/property-path' +import { absolute } from '../../../../utils/utils' +import type { CanvasCommand } from '../../commands/commands' +import { deleteProperties } from '../../commands/delete-properties-command' +import { reorderElement } from '../../commands/reorder-element-command' +import { showGridControls } from '../../commands/show-grid-controls-command' +import { controlsForGridPlaceholders } from '../../controls/grid-controls-for-strategies' +import type { CanvasStrategyFactory } from '../canvas-strategies' +import { onlyFitWhenDraggingThisControl } from '../canvas-strategies' +import type { InteractionCanvasState } from '../canvas-strategy-types' +import { + emptyStrategyApplicationResult, + getTargetPathsFromInteractionTarget, + strategyApplicationResult, +} from '../canvas-strategy-types' +import type { DragInteractionData, InteractionSession } from '../interaction-state' +import type { GridCellGlobalFrames } from './grid-helpers' +import { + findOriginalGrid, + getGridElementPinState, + getGridPositionIndex, + getOriginalElementGridConfiguration, + getParentGridTemplatesFromChildMeasurements, + gridMoveStrategiesExtraCommands, + isFlowGridChild, +} from './grid-helpers' +import { getTargetGridCellData } from '../../../inspector/grid-helpers' + +export const gridMoveReorderStrategy: CanvasStrategyFactory = ( + canvasState: InteractionCanvasState, + interactionSession: InteractionSession | null, +) => { + const selectedElements = getTargetPathsFromInteractionTarget(canvasState.interactionTarget) + if ( + selectedElements.length !== 1 || + interactionSession == null || + interactionSession.interactionData.type !== 'DRAG' || + interactionSession.interactionData.drag == null || + interactionSession.activeControl.type !== 'GRID_CELL_HANDLE' || + interactionSession.interactionData.modifiers.alt || + interactionSession.interactionData.modifiers.cmd // disable reorder when reparenting, for now (TODO) + ) { + return null + } + + const selectedElement = selectedElements[0] + if (!MetadataUtils.isGridCell(canvasState.startingMetadata, selectedElement)) { + return null + } + + const selectedElementMetadata = MetadataUtils.findElementByElementPath( + canvasState.startingMetadata, + selectedElement, + ) + if (selectedElementMetadata == null) { + return null + } + if (MetadataUtils.isPositionAbsolute(selectedElementMetadata)) { + return null + } + + const initialTemplates = getParentGridTemplatesFromChildMeasurements( + selectedElementMetadata.specialSizeMeasurements, + ) + if (initialTemplates == null) { + return null + } + + const parentGridPath = findOriginalGrid( + canvasState.startingMetadata, + EP.parentPath(selectedElement), + ) // TODO don't use EP.parentPath + if (parentGridPath == null) { + return null + } + + const gridFrame = MetadataUtils.findElementByElementPath( + canvasState.startingMetadata, + parentGridPath, + )?.globalFrame + if (gridFrame == null || isInfinityRectangle(gridFrame)) { + return null + } + + const elementGridPropertiesFromProps = + selectedElementMetadata.specialSizeMeasurements.elementGridPropertiesFromProps + + const pinnedState = getGridElementPinState(elementGridPropertiesFromProps ?? null) + const fitnessModifier = pinnedState !== 'pinned' ? 1 : -1 + + return { + id: 'reorder-grid-move-strategy', + name: 'Grid Reorder', + descriptiveLabel: 'Grid Reorder', + icon: { + category: 'tools', + type: 'pointer', + }, + controlsToRender: [controlsForGridPlaceholders(parentGridPath, 'visible-only-while-active')], + fitness: onlyFitWhenDraggingThisControl( + interactionSession, + 'GRID_CELL_HANDLE', + 2 + fitnessModifier, + ), + apply: () => { + if ( + interactionSession == null || + interactionSession.interactionData.type !== 'DRAG' || + interactionSession.interactionData.drag == null || + interactionSession.activeControl.type !== 'GRID_CELL_HANDLE' + ) { + return emptyStrategyApplicationResult + } + + const { commands, elementsToRerender } = getCommandsAndPatchForGridReorder( + canvasState, + interactionSession.interactionData, + selectedElement, + parentGridPath, + ) + if (commands.length === 0) { + return emptyStrategyApplicationResult + } + + const { midInteractionCommands, onCompleteCommands } = gridMoveStrategiesExtraCommands( + parentGridPath, + initialTemplates, + ) + + return strategyApplicationResult( + [...midInteractionCommands, ...onCompleteCommands, ...commands], + elementsToRerender, + ) + }, + } +} + +function getCommandsAndPatchForGridReorder( + canvasState: InteractionCanvasState, + interactionData: DragInteractionData, + selectedElement: ElementPath, + gridPath: ElementPath, +): { + commands: CanvasCommand[] + elementsToRerender: ElementPath[] +} { + if (interactionData.drag == null) { + return { commands: [], elementsToRerender: [] } + } + + const selectedElementMetadata = MetadataUtils.findElementByElementPath( + canvasState.startingMetadata, + selectedElement, + ) + if (selectedElementMetadata == null) { + return { commands: [], elementsToRerender: [] } + } + + const { parentGridCellGlobalFrames, parentContainerGridProperties } = + selectedElementMetadata.specialSizeMeasurements + if (parentGridCellGlobalFrames == null) { + return { commands: [], elementsToRerender: [] } + } + + const commands = runGridMoveReorder( + canvasState.startingMetadata, + interactionData, + selectedElementMetadata, + gridPath, + parentGridCellGlobalFrames, + parentContainerGridProperties, + ) + + return { + commands: commands, + elementsToRerender: [gridPath, selectedElement], + } +} + +function runGridMoveReorder( + jsxMetadata: ElementInstanceMetadataMap, + interactionData: DragInteractionData, + selectedElementMetadata: ElementInstanceMetadata, + gridPath: ElementPath, + gridCellGlobalFrames: GridCellGlobalFrames, + gridTemplate: GridContainerProperties, +): CanvasCommand[] { + if (interactionData.drag == null) { + return [] + } + + const mouseCellPosInOriginalElement = getOriginalElementGridConfiguration( + gridCellGlobalFrames, + interactionData, + selectedElementMetadata, + )?.mouseCellPosInOriginalElement + if (mouseCellPosInOriginalElement == null) { + return [] + } + + const targetGridCellData = getTargetGridCellData( + interactionData, + gridCellGlobalFrames, + mouseCellPosInOriginalElement, + ) + if (targetGridCellData == null) { + return [] + } + const { targetCellCoords, targetRootCell } = targetGridCellData + + const gridTemplateColumns = + gridTemplate.gridTemplateColumns?.type === 'DIMENSIONS' + ? gridTemplate.gridTemplateColumns.dimensions.length + : 1 + + const gridChildren = MetadataUtils.getChildrenUnordered(jsxMetadata, gridPath) + const gridFlowChildrenCount = gridChildren.filter(isFlowGridChild).length + + // The "pure" index in the grid children for the cell under mouse + const possiblyReorderIndex = getGridPositionIndex({ + row: targetCellCoords.row, + column: targetCellCoords.column, + gridTemplateColumns: gridTemplateColumns, + }) + + const canReorderToIndex = possiblyReorderIndex < gridFlowChildrenCount + + const updateGridControlsCommand = showGridControls( + 'mid-interaction', + gridPath, + targetCellCoords, + canReorderToIndex ? targetRootCell : null, + ) + + return [ + reorderElement('always', selectedElementMetadata.elementPath, absolute(possiblyReorderIndex)), + deleteProperties('always', selectedElementMetadata.elementPath, [ + PP.create('style', 'gridColumn'), + PP.create('style', 'gridRow'), + PP.create('style', 'gridColumnStart'), + PP.create('style', 'gridColumnEnd'), + PP.create('style', 'gridRowStart'), + PP.create('style', 'gridRowEnd'), + ]), + updateGridControlsCommand, + ] +} diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-rearrange-keyboard-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-rearrange-keyboard-strategy.ts index 8952cee616b8..9d5f8a20a1a5 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-rearrange-keyboard-strategy.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-rearrange-keyboard-strategy.ts @@ -10,7 +10,7 @@ import { strategyApplicationResult, } from '../canvas-strategy-types' import type { InteractionSession } from '../interaction-state' -import { setGridPropsCommands } from './grid-helpers' +import { findOriginalGrid, setGridPropsCommands } from './grid-helpers' import { getGridChildCellCoordBoundsFromCanvas } from './grid-cell-bounds' import { accumulatePresses } from './shared-keyboard-strategy-helpers' @@ -41,7 +41,10 @@ export function gridRearrangeResizeKeyboardStrategy( return null } - const parentGridPath = EP.parentPath(target) + const parentGridPath = findOriginalGrid(canvasState.startingMetadata, EP.parentPath(target)) // TODO don't use EP.parentPath + if (parentGridPath == null) { + return null + } const gridTemplate = cell.specialSizeMeasurements.parentContainerGridProperties const gridCellGlobalFrames = cell.specialSizeMeasurements.parentGridCellGlobalFrames 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 e293d4cd572d..ace5540d545f 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 @@ -12,7 +12,12 @@ import { selectComponentsForTest } from '../../../../utils/utils.test-utils' import CanvasActions from '../../canvas-actions' import { GridCellTestId } from '../../controls/grid-controls-for-strategies' import { CanvasControlsContainerID } from '../../controls/new-canvas-controls' -import { mouseDragFromPointToPoint, mouseUpAtPoint } from '../../event-helpers.test-utils' +import { + keyDown, + mouseDownAtPoint, + mouseMoveToPoint, + mouseUpAtPoint, +} from '../../event-helpers.test-utils' import type { EditorRenderResult } from '../../ui-jsx.test-utils' import { renderTestEditorWithCode } from '../../ui-jsx.test-utils' import type { GridCellCoordinates } from './grid-cell-bounds' @@ -84,7 +89,9 @@ describe('grid rearrange move strategy', () => { scale: 1, pathString: `sb/scene/grid/${testId}`, testId: testId, + tab: true, }) + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ gridColumnEnd: 'auto', gridColumnStart: '3', @@ -214,24 +221,21 @@ export var storyboard = ( const sourceRect = sourceGridCell.getBoundingClientRect() const targetRect = targetGridCell.getBoundingClientRect() - await mouseDragFromPointToPoint( - sourceGridCell, - { - x: sourceRect.x + 10, - y: sourceRect.y + 10, - }, - getRectCenter( - localRectangle({ - x: targetRect.x, - y: targetRect.y, - width: targetRect.width, - height: targetRect.height, - }), - ), - { - moveBeforeMouseDown: true, - }, + const dragFrom = { + x: sourceRect.x + 10, + y: sourceRect.y + 10, + } + const dragTo = getRectCenter( + localRectangle({ + x: targetRect.x, + y: targetRect.y, + width: targetRect.width, + height: targetRect.height, + }), ) + await mouseDownAtPoint(sourceGridCell, dragFrom) + await mouseMoveToPoint(sourceGridCell, dragTo) + await mouseUpAtPoint(sourceGridCell, dragTo) const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = editor.renderedDOM.getByTestId(testId).style @@ -275,8 +279,7 @@ export var storyboard = ( display: 'grid', gridTemplateColumns: '1fr 1fr', gridTemplateRows: '1fr 1fr', - border: '5px solid #000', - gridGap: 10, + gridGap: 0, }} >
{ @@ -366,22 +367,21 @@ export var storyboard = ( y: Math.floor(childBounds.top + childBounds.height / 2), }) - await mouseDragFromPointToPoint( - editor.renderedDOM.getByTestId(GridCellTestId(EP.fromString('sb/scene/grid/child'))), - childCenter, - offsetPoint(childCenter, windowPoint({ x: 240, y: 240 })), - { - moveBeforeMouseDown: true, - }, + const dragTarget = editor.renderedDOM.getByTestId( + GridCellTestId(EP.fromString('sb/scene/grid/child')), ) + const endPoint = offsetPoint(childCenter, windowPoint({ x: 240, y: 240 })) + await mouseDownAtPoint(dragTarget, childCenter) + await mouseMoveToPoint(dragTarget, endPoint) + await mouseUpAtPoint(dragTarget, endPoint) { const { top, left, gridColumn, gridRow } = child.style expect({ top, left, gridColumn, gridRow }).toEqual({ gridColumn: '1', gridRow: '1', - left: '137px', - top: '141px', + left: '132px', + top: '136px', }) } }) @@ -409,22 +409,22 @@ export var storyboard = ( y: Math.floor(childBounds.top + childBounds.height / 2), }) - await mouseDragFromPointToPoint( - editor.renderedDOM.getByTestId(GridCellTestId(EP.fromString('sb/scene/grid/child'))), - childCenter, - offsetPoint(childCenter, windowPoint({ x: 240, y: 240 })), - { - moveBeforeMouseDown: true, - }, + const endPoint = offsetPoint(childCenter, windowPoint({ x: 240, y: 240 })) + const dragTarget = editor.renderedDOM.getByTestId( + GridCellTestId(EP.fromString('sb/scene/grid/child')), ) + await mouseDownAtPoint(dragTarget, childCenter) + await mouseMoveToPoint(dragTarget, endPoint) + await mouseUpAtPoint(dragTarget, endPoint) + { const { top, left, gridColumn, gridRow } = child.style expect({ top, left, gridColumn, gridRow }).toEqual({ gridColumn: '2', gridRow: '2', - left: '26.5px', - top: '37px', + left: '21.5px', + top: '32px', }) } }) @@ -459,7 +459,6 @@ export var storyboard = ( display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', gridTemplateRows: '1fr 1fr 1fr 1fr', - border: '5px solid #000', gridGap: 10, }} > @@ -495,22 +494,22 @@ export var storyboard = ( y: Math.floor(childBounds.top + childBounds.height - 3), }) - await mouseDragFromPointToPoint( - editor.renderedDOM.getByTestId(GridCellTestId(EP.fromString('sb/scene/grid/child'))), - startPoint, - offsetPoint(startPoint, windowPoint({ x: -100, y: -100 })), - { - moveBeforeMouseDown: true, - }, + const endPoint = offsetPoint(startPoint, windowPoint({ x: -100, y: -100 })) + const dragTarget = editor.renderedDOM.getByTestId( + GridCellTestId(EP.fromString('sb/scene/grid/child')), ) + await mouseDownAtPoint(dragTarget, startPoint) + await mouseMoveToPoint(dragTarget, endPoint) + await mouseUpAtPoint(dragTarget, endPoint) + { const { top, left, gridColumn, gridRow } = child.style expect({ top, left, gridColumn, gridRow }).toEqual({ gridColumn: '1', gridRow: '1', - left: '61.5px', - top: '62px', + left: '59px', + top: '59.5px', }) } }) @@ -544,7 +543,6 @@ export var storyboard = ( display: 'grid', gridTemplateColumns: '1fr 1fr', gridTemplateRows: '1fr 1fr', - border: '5px solid #000', gridGap: 10, }} > @@ -588,23 +586,22 @@ export var storyboard = ( y: Math.floor(childBounds.top + childBounds.height / 2), }) - await mouseDragFromPointToPoint( - editor.renderedDOM.getByTestId(GridCellTestId(EP.fromString('sb/scene/grid/child'))), - childCenter, - offsetPoint(childCenter, windowPoint({ x: 280, y: 120 })), - { - staggerMoveEvents: false, - moveBeforeMouseDown: true, - }, + const dragTarget = editor.renderedDOM.getByTestId( + GridCellTestId(EP.fromString('sb/scene/grid/child')), ) + const endPoint = offsetPoint(childCenter, windowPoint({ x: 280, y: 120 })) + + await mouseDownAtPoint(dragTarget, childCenter) + await mouseMoveToPoint(dragTarget, endPoint) + await mouseUpAtPoint(dragTarget, endPoint) { const { top, left, gridColumn, gridRow } = child.style expect({ top, left, gridColumn, gridRow }).toEqual({ gridColumn: '', gridRow: '', - left: '297px', - top: '141px', + left: '292px', + top: '136px', }) } }) @@ -645,7 +642,13 @@ export var storyboard = ( const editor = await renderTestEditorWithCode(ProjectCodeReorder, 'await-first-dom-report') const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd, cells } = - await runReorderTest(editor, 'sb/scene/grid', 'orange', { row: 2, column: 2 }) + await runReorderTest( + editor, + 'sb/scene/grid', + 'orange', + { row: 2, column: 2 }, + { tab: true }, + ) expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ gridColumnEnd: 'auto', @@ -659,7 +662,13 @@ export var storyboard = ( it('reorders an element setting positioning also relative to other fixed elements', async () => { const editor = await renderTestEditorWithCode(ProjectCodeReorder, 'await-first-dom-report') - const first = await runReorderTest(editor, 'sb/scene/grid', 'orange', { row: 2, column: 2 }) + const first = await runReorderTest( + editor, + 'sb/scene/grid', + 'orange', + { row: 2, column: 2 }, + { tab: true }, + ) expect({ gridRowStart: first.gridRowStart, @@ -675,7 +684,13 @@ export var storyboard = ( expect(first.cells).toEqual(['pink', 'cyan', 'blue', 'orange']) - const second = await runReorderTest(editor, 'sb/scene/grid', 'pink', { row: 2, column: 3 }) + const second = await runReorderTest( + editor, + 'sb/scene/grid', + 'pink', + { row: 2, column: 3 }, + { tab: true }, + ) expect({ gridRowStart: second.gridRowStart, @@ -695,7 +710,13 @@ export var storyboard = ( it('reorders and removes positioning when moving back to contiguous', async () => { const editor = await renderTestEditorWithCode(ProjectCodeReorder, 'await-first-dom-report') - const first = await runReorderTest(editor, 'sb/scene/grid', 'orange', { row: 2, column: 2 }) + const first = await runReorderTest( + editor, + 'sb/scene/grid', + 'orange', + { row: 2, column: 2 }, + { tab: true }, + ) expect({ gridRowStart: first.gridRowStart, gridRowEnd: first.gridRowEnd, @@ -710,7 +731,13 @@ export var storyboard = ( expect(first.cells).toEqual(['pink', 'cyan', 'blue', 'orange']) - const second = await runReorderTest(editor, 'sb/scene/grid', 'orange', { row: 1, column: 1 }) + const second = await runReorderTest( + editor, + 'sb/scene/grid', + 'orange', + { row: 1, column: 1 }, + { tab: true }, + ) expect({ gridRowStart: second.gridRowStart, @@ -733,7 +760,13 @@ export var storyboard = ( 'await-first-dom-report', ) - const result = await runReorderTest(editor, 'sb/scene/grid', 'orange', { row: 1, column: 1 }) + const result = await runReorderTest( + editor, + 'sb/scene/grid', + 'orange', + { row: 1, column: 1 }, + { tab: true }, + ) expect({ gridRowStart: result.gridRowStart, gridRowEnd: result.gridRowEnd, @@ -759,6 +792,7 @@ async function runMoveTest( testId: string targetCell?: GridCellCoordinates draggedCell?: GridCellCoordinates + tab?: boolean }, ) { const elementPathToDrag = EP.fromString(props.pathString) @@ -789,6 +823,10 @@ async function runMoveTest( const sourceRect = sourceGridCell.getBoundingClientRect() const targetRect = targetGridCell.getBoundingClientRect() + const dragFrom = { + x: sourceRect.x + 10, + y: sourceRect.y + 10, + } const endPoint = getRectCenter( localRectangle({ x: targetRect.x, @@ -798,18 +836,11 @@ async function runMoveTest( }), ) - await mouseDragFromPointToPoint( - sourceGridCell, - { - x: sourceRect.x + 10, - y: sourceRect.y + 10, - }, - endPoint, - { - staggerMoveEvents: false, - moveBeforeMouseDown: true, - }, - ) + await mouseDownAtPoint(sourceGridCell, dragFrom) + await mouseMoveToPoint(sourceGridCell, endPoint) + if (props.tab) { + await keyDown('Tab') + } await mouseUpAtPoint(editor.renderedDOM.getByTestId(CanvasControlsContainerID), endPoint) return editor.renderedDOM.getByTestId(props.testId).style @@ -820,12 +851,14 @@ async function runReorderTest( gridTestId: string, testId: string, targetCell: GridCellCoordinates, + options?: { tab?: boolean }, ) { const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runMoveTest(editor, { scale: 1, pathString: `${gridTestId}/${testId}`, testId: testId, targetCell: targetCell, + tab: options?.tab, }) const element = editor.getEditorState().editor.jsxMetadata[gridTestId] 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 deleted file mode 100644 index 430007657cc5..000000000000 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-rearrange-move-strategy.ts +++ /dev/null @@ -1,262 +0,0 @@ -import type { ElementPath } from 'utopia-shared/src/types' -import { MetadataUtils } from '../../../../core/model/element-metadata-utils' -import * as EP from '../../../../core/shared/element-path' -import { controlsForGridPlaceholders } from '../../controls/grid-controls-for-strategies' -import type { - ElementInstanceMetadataMap, - GridAutoOrTemplateBase, -} from '../../../../core/shared/element-template' -import * as PP from '../../../../core/shared/property-path' -import { printGridAutoOrTemplateBase } from '../../../inspector/common/css-utils' -import type { PropertyToUpdate } from '../../commands/set-property-command' -import { - propertyToDelete, - propertyToSet, - updateBulkProperties, -} from '../../commands/set-property-command' -import type { CanvasStrategyFactory } from '../canvas-strategies' -import { onlyFitWhenDraggingThisControl } from '../canvas-strategies' -import type { ControlWithProps, InteractionCanvasState } from '../canvas-strategy-types' -import { - getTargetPathsFromInteractionTarget, - emptyStrategyApplicationResult, - strategyApplicationResult, -} from '../canvas-strategy-types' -import type { DragInteractionData, InteractionSession } from '../interaction-state' -import { runGridRearrangeMove } from './grid-helpers' -import { isInfinityRectangle } from '../../../../core/shared/math-utils' -import type { CanvasCommand } from '../../commands/commands' - -export const gridRearrangeMoveStrategy: CanvasStrategyFactory = ( - canvasState: InteractionCanvasState, - interactionSession: InteractionSession | null, -) => { - const selectedElements = getTargetPathsFromInteractionTarget(canvasState.interactionTarget) - if ( - selectedElements.length === 0 || - interactionSession == null || - interactionSession.interactionData.type !== 'DRAG' || - interactionSession.interactionData.drag == null || - interactionSession.activeControl.type !== 'GRID_CELL_HANDLE' || - interactionSession.interactionData.modifiers.alt - ) { - return null - } - - const selectedElement = selectedElements[0] - if (!MetadataUtils.isGridCell(canvasState.startingMetadata, selectedElement)) { - return null - } - - const parentGridPath = EP.parentPath(selectedElement) - const gridFrame = MetadataUtils.findElementByElementPath( - canvasState.startingMetadata, - parentGridPath, - )?.globalFrame - if (gridFrame == null || isInfinityRectangle(gridFrame)) { - return null - } - - const initialTemplates = getGridTemplates(canvasState.startingMetadata, parentGridPath) - if (initialTemplates == null) { - return null - } - - const strategyToApply = getStrategyToApply( - interactionSession.interactionData, - parentGridPath, - canvasState.startingMetadata, - selectedElement, - ) - if (strategyToApply == null) { - return null - } - - return { - id: 'rearrange-grid-move-strategy', - name: strategyToApply.name, - descriptiveLabel: strategyToApply.name, - icon: { - category: 'tools', - type: 'pointer', - }, - controlsToRender: strategyToApply.controlsToRender, - fitness: onlyFitWhenDraggingThisControl(interactionSession, 'GRID_CELL_HANDLE', 2), - apply: (strategyLifecycle) => { - if ( - interactionSession == null || - interactionSession.interactionData.type !== 'DRAG' || - interactionSession.interactionData.drag == null || - interactionSession.activeControl.type !== 'GRID_CELL_HANDLE' - ) { - return emptyStrategyApplicationResult - } - - const midInteractionCommands = [ - // during the interaction, freeze the template with the calculated values… - updateBulkProperties('mid-interaction', parentGridPath, [ - propertyToSet( - PP.create('style', 'gridTemplateColumns'), - printGridAutoOrTemplateBase(initialTemplates.calculated.columns), - ), - propertyToSet( - PP.create('style', 'gridTemplateRows'), - printGridAutoOrTemplateBase(initialTemplates.calculated.rows), - ), - ]), - ] - - const onCompleteCommands = [ - // …eventually, restore the grid template on complete. - updateBulkProperties( - 'on-complete', - parentGridPath, - restoreGridTemplateFromProps(initialTemplates.fromProps), - ), - ] - - const { commands, elementsToRerender } = getCommandsAndPatchForGridRearrange( - canvasState, - interactionSession.interactionData, - selectedElement, - ) - - if (commands.length === 0) { - return emptyStrategyApplicationResult - } - - return strategyApplicationResult( - [...midInteractionCommands, ...onCompleteCommands, ...commands], - elementsToRerender, - ) - }, - } -} - -function getCommandsAndPatchForGridRearrange( - canvasState: InteractionCanvasState, - interactionData: DragInteractionData, - selectedElement: ElementPath, -): { - commands: CanvasCommand[] - elementsToRerender: ElementPath[] -} { - if (interactionData.drag == null) { - return { commands: [], elementsToRerender: [] } - } - - const selectedElementMetadata = MetadataUtils.findElementByElementPath( - canvasState.startingMetadata, - selectedElement, - ) - if (selectedElementMetadata == null) { - return { commands: [], elementsToRerender: [] } - } - - const { parentGridCellGlobalFrames, parentContainerGridProperties } = - selectedElementMetadata.specialSizeMeasurements - - const commands = - parentGridCellGlobalFrames != null - ? runGridRearrangeMove( - selectedElement, - selectedElement, - canvasState.startingMetadata, - interactionData, - parentGridCellGlobalFrames, - parentContainerGridProperties, - ) - : [] - - return { - commands: commands, - elementsToRerender: [EP.parentPath(selectedElement), selectedElement], - } -} - -function restoreGridTemplateFromProps(params: { - columns: GridAutoOrTemplateBase - rows: GridAutoOrTemplateBase -}): PropertyToUpdate[] { - let properties: PropertyToUpdate[] = [] - const newCols = printGridAutoOrTemplateBase(params.columns) - const newRows = printGridAutoOrTemplateBase(params.rows) - if (newCols === '') { - properties.push(propertyToDelete(PP.create('style', 'gridTemplateColumns'))) - } else { - properties.push(propertyToSet(PP.create('style', 'gridTemplateColumns'), newCols)) - } - if (newRows === '') { - properties.push(propertyToDelete(PP.create('style', 'gridTemplateRows'))) - } else { - properties.push(propertyToSet(PP.create('style', 'gridTemplateRows'), newRows)) - } - return properties -} - -function getGridTemplates(jsxMetadata: ElementInstanceMetadataMap, gridPath: ElementPath) { - const grid = MetadataUtils.findElementByElementPath(jsxMetadata, gridPath) - if (grid == null) { - return null - } - - const templateFromProps = grid.specialSizeMeasurements.containerGridPropertiesFromProps - const templateRowsFromProps = templateFromProps.gridTemplateRows - if (templateRowsFromProps == null) { - return null - } - const templateColsFromProps = templateFromProps.gridTemplateColumns - if (templateColsFromProps == null) { - return null - } - - const templateCalculated = grid.specialSizeMeasurements.containerGridProperties - const templateRowsCalculated = templateCalculated.gridTemplateRows - if (templateRowsCalculated == null) { - return null - } - const templateColsCalculated = templateCalculated.gridTemplateColumns - if (templateColsCalculated == null) { - return null - } - - return { - calculated: { - columns: templateColsCalculated, - rows: templateRowsCalculated, - }, - fromProps: { - columns: templateColsFromProps, - rows: templateRowsFromProps, - }, - } -} - -type StrategyToApply = { - controlsToRender: ControlWithProps[] - name: string -} - -function getStrategyToApply( - interactionData: DragInteractionData, - parentGridPath: ElementPath, - jsxMetadata: ElementInstanceMetadataMap, - cell: ElementPath, -): StrategyToApply | null { - if (interactionData.drag == null) { - return null - } - - const element = MetadataUtils.findElementByElementPath(jsxMetadata, cell) - - const name = - MetadataUtils.isPositionAbsolute(element) && - !MetadataUtils.isGridCellWithPositioning(jsxMetadata, cell) - ? 'Grid Move (Abs)' - : 'Rearrange Grid (Move)' - - return { - name: name, - controlsToRender: [controlsForGridPlaceholders(parentGridPath, 'visible-only-while-active')], - } -} diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-reparent-strategies.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/grid-reparent-strategies.spec.browser2.tsx index 313c9b8deee0..b1e296f1aec2 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-reparent-strategies.spec.browser2.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-reparent-strategies.spec.browser2.tsx @@ -847,6 +847,8 @@ describe('grid reparent strategies', () => { backgroundColor: '#f0f', width: 79, height: 86, + gridColumn: 3, + gridRow: 1, }} data-uid='dragme' data-testid='dragme' diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-reparent-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/grid-reparent-strategy.tsx index 2f8ee083020b..968c3ee88b3d 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-reparent-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-reparent-strategy.tsx @@ -4,10 +4,7 @@ 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 { ElementPathTrees } from '../../../../core/shared/element-path-tree' -import { - type ElementInstanceMetadata, - type ElementInstanceMetadataMap, -} from '../../../../core/shared/element-template' +import { type ElementInstanceMetadataMap } from '../../../../core/shared/element-template' import type { CanvasRectangle } from '../../../../core/shared/math-utils' import { isInfinityRectangle } from '../../../../core/shared/math-utils' import type { ElementPath } from '../../../../core/shared/project-file-types' @@ -38,7 +35,7 @@ import { import type { DragInteractionData, InteractionSession, UpdatedPathMap } from '../interaction-state' import { honoursPropsPosition, shouldKeepMovingDraggedGroupChildren } from './absolute-utils' import { replaceFragmentLikePathsWithTheirChildrenRecursive } from './fragment-like-helpers' -import { runGridRearrangeMove } from './grid-helpers' +import { runGridMoveRearrange } from './grid-move-rearrange-strategy' import { ifAllowedToReparent, isAllowedToReparent } from './reparent-helpers/reparent-helpers' import { removeAbsolutePositioningProps } from './reparent-helpers/reparent-property-changes' import type { ReparentTarget } from './reparent-helpers/reparent-strategy-helpers' @@ -268,21 +265,34 @@ function gridReparentCommands( jsxMetadata, newParent.intendedParentPath, ) + if (targetParent == null) { + return null + } + const gridCellGlobalFrames = targetParent?.specialSizeMeasurements.gridCellGlobalFrames ?? null + if (gridCellGlobalFrames == null) { + return null + } + const gridTemplate = targetParent?.specialSizeMeasurements.containerGridProperties ?? null + if (gridTemplate == null) { + return null + } - const gridPropsCommands = - gridCellGlobalFrames != null && gridTemplate != null - ? runGridRearrangeMove( - target, - target, - jsxMetadata, - interactionData, - gridCellGlobalFrames, - gridTemplate, - newPath, - ) - : [] + const targetElement = MetadataUtils.findElementByElementPath(jsxMetadata, target) + if (targetElement == null) { + return null + } + + const gridPropsCommands = runGridMoveRearrange( + jsxMetadata, + interactionData, + targetElement, + targetParent.elementPath, + gridCellGlobalFrames, + gridTemplate, + newPath, + ) const removeAbsolutePositioningPropsCommands = removeAbsolutePositioningProps('always', newPath) diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts index d315bbadc7d6..187915c80452 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts @@ -20,7 +20,7 @@ import { strategyApplicationResult, } from '../canvas-strategy-types' import type { InteractionSession } from '../interaction-state' -import { setGridPropsCommands } from './grid-helpers' +import { findOriginalGrid, setGridPropsCommands } from './grid-helpers' import { resizeBoundingBoxFromSide } from './resize-helpers' export const gridResizeElementStrategy: CanvasStrategyFactory = ( @@ -60,7 +60,13 @@ export const gridResizeElementStrategy: CanvasStrategyFactory = ( return null } - const parentGridPath = EP.parentPath(selectedElement) + const parentGridPath = findOriginalGrid( + canvasState.startingMetadata, + EP.parentPath(selectedElement), + ) // TODO don't use EP.parentPath + if (parentGridPath == null) { + return null + } return { id: 'GRID-CELL-RESIZE-STRATEGY', diff --git a/editor/src/components/canvas/canvas-strategies/strategies/rearrange-grid-swap-strategy.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/rearrange-grid-swap-strategy.spec.browser2.tsx deleted file mode 100644 index 42293637b623..000000000000 --- a/editor/src/components/canvas/canvas-strategies/strategies/rearrange-grid-swap-strategy.spec.browser2.tsx +++ /dev/null @@ -1,208 +0,0 @@ -import { getPrintedUiJsCode, renderTestEditorWithCode } from '../../ui-jsx.test-utils' -import * as EP from '../../../../core/shared/element-path' -import { selectComponents } from '../../../../components/editor/actions/meta-actions' -import { CanvasControlsContainerID } from '../../controls/new-canvas-controls' -import { - mouseDownAtPoint, - mouseMoveToPoint, - mouseUpAtPoint, - pressKey, -} from '../../event-helpers.test-utils' -import { canvasPoint } from '../../../../core/shared/math-utils' -import { GridCellTestId } from '../../controls/grid-controls-for-strategies' - -const testProject = ` -import * as React from 'react' -import { Storyboard } from 'utopia-api' - -export var storyboard = ( - -
-
-
-
-
-
-
-
-
-
-
- -) -` - -describe('swap an element', () => { - it('swap out an element for another from a different column and row', async () => { - const renderResult = await renderTestEditorWithCode(testProject, 'await-first-dom-report') - const draggedItem = EP.fromString(`sb/grid/row-1-column-2`) - await renderResult.dispatch(selectComponents([draggedItem], false), true) - await renderResult.getDispatchFollowUpActionsFinished() - const canvasControlsLayer = renderResult.renderedDOM.getByTestId(CanvasControlsContainerID) - const draggedItemElement = renderResult.renderedDOM.getByTestId( - GridCellTestId(EP.fromString(`sb/grid/row-1-column-2`)), - ) - const draggedItemRect = draggedItemElement.getBoundingClientRect() - const startPoint = canvasPoint({ - x: draggedItemRect.x + draggedItemRect.width / 2, - y: draggedItemRect.y + draggedItemRect.height / 2, - }) - const swapTargetElement = renderResult.renderedDOM.getByTestId( - GridCellTestId(EP.fromString(`sb/grid/row-2-column-1`)), - ) - const swapTargetRect = swapTargetElement.getBoundingClientRect() - const endPoint = canvasPoint({ - x: swapTargetRect.x + swapTargetRect.width / 2, - y: swapTargetRect.y + swapTargetRect.height / 2, - }) - await mouseMoveToPoint(draggedItemElement, startPoint) - await mouseDownAtPoint(draggedItemElement, startPoint) - await mouseMoveToPoint(canvasControlsLayer, endPoint) - await pressKey('Tab') - await renderResult.getDispatchFollowUpActionsFinished() - expect( - renderResult.getEditorState().editor.canvas.interactionSession?.userPreferredStrategy, - ).toEqual('rearrange-grid-swap-strategy') - await mouseUpAtPoint(canvasControlsLayer, endPoint) - await renderResult.getDispatchFollowUpActionsFinished() - - expect(getPrintedUiJsCode(renderResult.getEditorState())) - .toEqual(`import * as React from 'react' -import { Storyboard } from 'utopia-api' - -export var storyboard = ( - -
-
-
-
-
-
-
-
-
-
-
- -) -`) - }) -}) diff --git a/editor/src/components/canvas/canvas-strategies/strategies/rearrange-grid-swap-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/rearrange-grid-swap-strategy.ts deleted file mode 100644 index ffaebb2d3f6b..000000000000 --- a/editor/src/components/canvas/canvas-strategies/strategies/rearrange-grid-swap-strategy.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { MetadataUtils } from '../../../../core/model/element-metadata-utils' -import * as EP from '../../../../core/shared/element-path' -import type { - ElementInstanceMetadata, - GridContainerProperties, -} from '../../../../core/shared/element-template' -import { - isFiniteRectangle, - offsetPoint, - rectContainsPointInclusive, -} from '../../../../core/shared/math-utils' -import type { ElementPath } from '../../../../core/shared/project-file-types' -import { create } from '../../../../core/shared/property-path' -import type { CanvasCommand } from '../../commands/commands' -import { deleteProperties } from '../../commands/delete-properties-command' -import { rearrangeChildren } from '../../commands/rearrange-children-command' -import { controlsForGridPlaceholders } from '../../controls/grid-controls-for-strategies' -import { recurseIntoChildrenOfMapOrFragment } from '../../gap-utils' -import type { CanvasStrategyFactory } from '../canvas-strategies' -import { onlyFitWhenDraggingThisControl } from '../canvas-strategies' -import type { InteractionCanvasState } from '../canvas-strategy-types' -import { - getTargetPathsFromInteractionTarget, - emptyStrategyApplicationResult, - strategyApplicationResult, -} from '../canvas-strategy-types' -import type { InteractionSession } from '../interaction-state' -import { setGridPropsCommands } from './grid-helpers' - -export const rearrangeGridSwapStrategy: CanvasStrategyFactory = ( - canvasState: InteractionCanvasState, - interactionSession: InteractionSession | null, -) => { - const selectedElements = getTargetPathsFromInteractionTarget(canvasState.interactionTarget) - if ( - selectedElements.length !== 1 || - interactionSession == null || - interactionSession.interactionData.type !== 'DRAG' || - interactionSession.interactionData.drag == null || - interactionSession.activeControl.type !== 'GRID_CELL_HANDLE' || - interactionSession.interactionData.modifiers.alt || - interactionSession.interactionData.modifiers.cmd - ) { - return null - } - - const selectedElement = selectedElements[0] - - if (!MetadataUtils.isGridCellWithPositioning(canvasState.startingMetadata, selectedElement)) { - return null - } - - const children = recurseIntoChildrenOfMapOrFragment( - canvasState.startingMetadata, - canvasState.startingAllElementProps, - canvasState.startingElementPathTree, - EP.parentPath(selectedElement), - ) - - const parentGridPath = EP.parentPath(selectedElement) - - return { - id: 'rearrange-grid-swap-strategy', - name: 'Rearrange Grid (Swap)', - descriptiveLabel: 'Rearrange Grid (Swap)', - icon: { - category: 'tools', - type: 'pointer', - }, - controlsToRender: [controlsForGridPlaceholders(parentGridPath)], - fitness: onlyFitWhenDraggingThisControl(interactionSession, 'GRID_CELL_HANDLE', 1), - apply: () => { - if ( - interactionSession == null || - interactionSession.interactionData.type !== 'DRAG' || - interactionSession.interactionData.drag == null || - interactionSession.activeControl.type !== 'GRID_CELL_HANDLE' - ) { - return emptyStrategyApplicationResult - } - - const pointOnCanvas = offsetPoint( - interactionSession.interactionData.dragStart, - interactionSession.interactionData.drag, - ) - - const pointerOverChild = children.find( - (c) => - c.globalFrame != null && - isFiniteRectangle(c.globalFrame) && - rectContainsPointInclusive(c.globalFrame, pointOnCanvas), - ) - - const container = MetadataUtils.findElementByElementPath( - canvasState.startingMetadata, - EP.parentPath(selectedElement), - ) - if (container == null) { - return emptyStrategyApplicationResult - } - const gridTemplate = container.specialSizeMeasurements.containerGridProperties - - let commands: CanvasCommand[] = [] - - if ( - pointerOverChild != null && - EP.toUid(pointerOverChild.elementPath) !== EP.toUid(interactionSession.activeControl.path) - ) { - commands.push( - ...swapChildrenCommands({ - grabbedElementUid: EP.toUid(interactionSession.activeControl.path), - swapToElementUid: EP.toUid(pointerOverChild.elementPath), - children: children, - parentPath: EP.parentPath(selectedElement), - gridTemplate: gridTemplate, - }), - ) - } - - if (commands == null) { - return emptyStrategyApplicationResult - } - - return strategyApplicationResult(commands, [EP.parentPath(selectedElement)]) - }, - } -} - -export const GridPositioningProps: Array = [ - 'gridColumn', - 'gridRow', - 'gridColumnStart', - 'gridColumnEnd', - 'gridRowStart', - 'gridRowEnd', -] - -function swapChildrenCommands({ - grabbedElementUid, - swapToElementUid, - children, - parentPath, - gridTemplate, -}: { - grabbedElementUid: string - swapToElementUid: string - children: ElementInstanceMetadata[] - parentPath: ElementPath - gridTemplate: GridContainerProperties -}): CanvasCommand[] { - const grabbedElement = children.find((c) => EP.toUid(c.elementPath) === grabbedElementUid) - const swapToElement = children.find((c) => EP.toUid(c.elementPath) === swapToElementUid) - - if (grabbedElement == null || swapToElement == null) { - return [] - } - - const rearrangedChildren = children - .map((c) => { - if (EP.pathsEqual(c.elementPath, grabbedElement.elementPath)) { - return swapToElement.elementPath - } - if (EP.pathsEqual(c.elementPath, swapToElement.elementPath)) { - return grabbedElement.elementPath - } - return c.elementPath - }) - .map((path) => EP.dynamicPathToStaticPath(path)) - - return [ - rearrangeChildren('always', parentPath, rearrangedChildren), - deleteProperties( - 'always', - swapToElement.elementPath, - GridPositioningProps.map((p) => create('style', p)), - ), - deleteProperties( - 'always', - grabbedElement.elementPath, - GridPositioningProps.map((p) => create('style', p)), - ), - ...setGridPropsCommands( - grabbedElement.elementPath, - gridTemplate, - swapToElement.specialSizeMeasurements.elementGridProperties, - ), - ...setGridPropsCommands( - swapToElement.elementPath, - gridTemplate, - grabbedElement.specialSizeMeasurements.elementGridProperties, - ), - ] -} diff --git a/editor/src/components/canvas/canvas-strategies/strategies/resize-grid-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/resize-grid-strategy.ts index 4c58e3d97ad9..95733b2426fd 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/resize-grid-strategy.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/resize-grid-strategy.ts @@ -87,10 +87,10 @@ export const resizeGridStrategy: CanvasStrategyFactory = ( return null } - const gridPath = isGrid ? selectedElement : EP.parentPath(selectedElement) - const metadata = interactionSession?.latestMetadata ?? canvasState.startingMetadata - const originalGridPath = findOriginalGrid(metadata, gridPath) - if (originalGridPath == null) { + const gridPath = isGrid + ? selectedElement + : findOriginalGrid(canvasState.startingMetadata, EP.parentPath(selectedElement)) // TODO don't use EP.parentPath + if (gridPath == null) { return null } @@ -131,7 +131,7 @@ export const resizeGridStrategy: CanvasStrategyFactory = ( const dragAmount = control.axis === 'column' ? drag.x : drag.y const gridSpecialSizeMeasurements = - canvasState.startingMetadata[EP.toString(originalGridPath)].specialSizeMeasurements + canvasState.startingMetadata[EP.toString(gridPath)].specialSizeMeasurements const originalValues = control.axis === 'column' @@ -242,11 +242,11 @@ export const resizeGridStrategy: CanvasStrategyFactory = ( } let commands: CanvasCommand[] = [ - updateBulkProperties('always', originalGridPath, propertiesToUpdate), + updateBulkProperties('always', gridPath, propertiesToUpdate), setCursorCommand(control.axis === 'column' ? CSSCursor.ColResize : CSSCursor.RowResize), ] - return strategyApplicationResult(commands, [originalGridPath]) + return strategyApplicationResult(commands, [gridPath]) }, } } diff --git a/editor/src/components/canvas/controls/grid-controls.tsx b/editor/src/components/canvas/controls/grid-controls.tsx index 2cb226ce961e..7491cc5a9ef8 100644 --- a/editor/src/components/canvas/controls/grid-controls.tsx +++ b/editor/src/components/canvas/controls/grid-controls.tsx @@ -65,6 +65,7 @@ import { gridCellTargetId } from '../canvas-strategies/strategies/grid-cell-boun import { getGlobalFrameOfGridCellFromMetadata, getGridRelatedIndexes, + getGridElementPinState, } from '../canvas-strategies/strategies/grid-helpers' import { canResizeGridTemplate } from '../canvas-strategies/strategies/resize-grid-strategy' import { resizeBoundingBoxFromSide } from '../canvas-strategies/strategies/resize-helpers' @@ -677,6 +678,19 @@ const GridControl = React.memo(({ grid, controlsVisible }) => 'GridControl anyTargetAbsolute', ) + const anyTargetNotPinned = useEditorState( + Substores.metadata, + (store) => + store.editor.selectedViews.some( + (elementPath) => + getGridElementPinState( + MetadataUtils.findElementByElementPath(store.editor.jsxMetadata, elementPath) + ?.specialSizeMeasurements.elementGridPropertiesFromProps ?? null, + ) !== 'pinned', + ), + 'GridControl anyTargetNotPinned', + ) + const scale = useEditorState( Substores.canvas, (store) => store.editor.canvas.scale, @@ -840,7 +854,7 @@ const GridControl = React.memo(({ grid, controlsVisible }) => ) useCellAnimation({ - disabled: anyTargetAbsolute, + disabled: anyTargetAbsolute || anyTargetNotPinned, targetRootCell: targetRootCell, controls: controls, shadowFrame: initialShadowFrame, @@ -862,7 +876,15 @@ const GridControl = React.memo(({ grid, controlsVisible }) => }`, } - const dontShowActiveCellHighlight = !targetsAreCellsWithPositioning && anyTargetAbsolute + const targetRootCellIsValidTarget = useEditorState( + Substores.canvas, + (store) => store.editor.canvas.controls.gridControlData?.rootCell != null, + 'GridControl targetRootCellIsValidTarget', + ) + + const dontShowActiveCellHighlight = + (!targetsAreCellsWithPositioning && anyTargetAbsolute) || + (anyTargetNotPinned && !targetRootCellIsValidTarget) return ( @@ -1440,7 +1462,7 @@ function useCellAnimation(params: { const selectedViews = useEditorState( Substores.selectedViews, (store) => store.editor.selectedViews, - 'useSnapAnimation selectedViews', + 'useCellAnimation selectedViews', ) const animate = useCanvasAnimation(selectedViews) diff --git a/editor/src/components/canvas/event-helpers.test-utils.tsx b/editor/src/components/canvas/event-helpers.test-utils.tsx index 11f37d4f63fb..1f5e1c50e768 100644 --- a/editor/src/components/canvas/event-helpers.test-utils.tsx +++ b/editor/src/components/canvas/event-helpers.test-utils.tsx @@ -205,6 +205,7 @@ export async function mouseDragFromPointToPoint( moveBeforeMouseDown?: boolean skipMouseUp?: boolean realMouseDown?: boolean + tab?: boolean } = {}, ): Promise { const { buttons, ...mouseUpOptions } = options.eventOptions ?? {} @@ -271,6 +272,10 @@ export async function mouseDragFromPointToPoint( ) } + if (options.tab) { + await keyDown('Tab') + } + if (options.midDragCallback != null) { await options.midDragCallback() } diff --git a/editor/src/components/inspector/grid-helpers.ts b/editor/src/components/inspector/grid-helpers.ts index ef8be9bff817..3b91b1feb7a1 100644 --- a/editor/src/components/inspector/grid-helpers.ts +++ b/editor/src/components/inspector/grid-helpers.ts @@ -16,6 +16,14 @@ import { isGridCSSNumber, type GridDimension, } from './common/css-utils' +import type { DragInteractionData } from '../canvas/canvas-strategies/interaction-state' +import { offsetPoint } from '../../core/shared/math-utils' +import type { GridCellCoordinates } from '../canvas/canvas-strategies/strategies/grid-cell-bounds' +import { + getClosestGridCellToPoint, + gridCellCoordinates, +} from '../canvas/canvas-strategies/strategies/grid-cell-bounds' +import type { GridCellGlobalFrames } from '../canvas/canvas-strategies/strategies/grid-helpers' export const useGridExpressionInputFocused = () => { const [focused, setFocused] = React.useState(false) @@ -49,3 +57,44 @@ export const gridDimensionDropdownKeywords = [ { label: 'Min-Content', value: cssKeyword('min-content') }, { label: 'Max-Content', value: cssKeyword('max-content') }, ] + +export const GridPositioningProps: Array = [ + 'gridColumn', + 'gridRow', + 'gridColumnStart', + 'gridColumnEnd', + 'gridRowStart', + 'gridRowEnd', +] + +export function getTargetGridCellData( + interactionData: DragInteractionData, + gridCellGlobalFrames: GridCellGlobalFrames, + mouseCellPosInOriginalElement: GridCellCoordinates, +): { + targetCellCoords: GridCellCoordinates + targetRootCell: GridCellCoordinates +} | null { + if (interactionData.drag == null) { + return null + } + const mousePos = offsetPoint(interactionData.dragStart, interactionData.drag) + const targetCellData = getClosestGridCellToPoint(gridCellGlobalFrames, mousePos) + if (targetCellData == null) { + return null + } + const targetCellCoords = targetCellData?.gridCellCoordinates + if (targetCellCoords == null) { + return null + } + + const row = Math.max(targetCellCoords.row - mouseCellPosInOriginalElement.row, 1) + const column = Math.max(targetCellCoords.column - mouseCellPosInOriginalElement.column, 1) + + const targetRootCell = gridCellCoordinates(row, column) + + return { + targetCellCoords, + targetRootCell, + } +} diff --git a/editor/src/components/inspector/sections/style-section/container-subsection/grid-cell-subsection.tsx b/editor/src/components/inspector/sections/style-section/container-subsection/grid-cell-subsection.tsx index 3f206d40aab6..15141b3f314a 100644 --- a/editor/src/components/inspector/sections/style-section/container-subsection/grid-cell-subsection.tsx +++ b/editor/src/components/inspector/sections/style-section/container-subsection/grid-cell-subsection.tsx @@ -39,7 +39,7 @@ import { } from '../../../common/css-utils' import { UIGridRow } from '../../../widgets/ui-grid-row' import { deleteProperties } from '../../../../canvas/commands/delete-properties-command' -import { GridPositioningProps } from '../../../../canvas/canvas-strategies/strategies/rearrange-grid-swap-strategy' +import { GridPositioningProps } from '../../../grid-helpers' import { styleP } from '../../../inspector-common' type CellAdjustMode = 'dimensions' | 'boundaries'