From 2597294ed08de124af2a36e0c1add02e36c6f8c7 Mon Sep 17 00:00:00 2001 From: Federico Ruggi <1081051+ruggi@users.noreply.github.com> Date: Tue, 27 Aug 2024 09:47:39 +0200 Subject: [PATCH] Reparent into grid (#6258) **Problem:** It's not possible to correctly drag-to-reparent into a grid. **Fix:** Allow dragging elements into a grid, positioning them as part of the grid hierarchy. | Before | After | |-------|----------| | | | Fixes #6257 --- .../post-action-options/post-action-paste.ts | 21 +- .../strategies/absolute-reparent-strategy.tsx | 5 +- .../drag-to-insert-metastrategy.tsx | 5 +- .../draw-to-insert-metastrategy.tsx | 11 +- .../strategies/grid-reparent-strategy.tsx | 248 ++++++++++++++++++ .../reparent-property-changes.ts | 20 +- .../reparent-strategy-helpers.ts | 4 +- .../reparent-strategy-parent-lookup.ts | 19 +- .../strategies/reparent-metastrategy.tsx | 157 ++++++----- .../components/editor/wrap-in-callbacks.ts | 2 + 10 files changed, 392 insertions(+), 100 deletions(-) create mode 100644 editor/src/components/canvas/canvas-strategies/strategies/grid-reparent-strategy.tsx diff --git a/editor/src/components/canvas/canvas-strategies/post-action-options/post-action-paste.ts b/editor/src/components/canvas/canvas-strategies/post-action-options/post-action-paste.ts index 283bbdacce1b..c0a1947c5a75 100644 --- a/editor/src/components/canvas/canvas-strategies/post-action-options/post-action-paste.ts +++ b/editor/src/components/canvas/canvas-strategies/post-action-options/post-action-paste.ts @@ -14,7 +14,6 @@ import { stripNulls, zip } from '../../../../core/shared/array-utils' import type { Either } from '../../../../core/shared/either' import { isLeft, left, right } from '../../../../core/shared/either' import * as EP from '../../../../core/shared/element-path' -import * as PP from '../../../../core/shared/property-path' import type { ElementPathTrees } from '../../../../core/shared/element-path-tree' import type { ElementInstanceMetadataMap } from '../../../../core/shared/element-template' import { @@ -54,7 +53,6 @@ import type { CanvasCommand } from '../../commands/commands' import { foldAndApplyCommandsInner } from '../../commands/commands' import { deleteElement } from '../../commands/delete-element-command' import { queueTrueUpElement } from '../../commands/queue-true-up-command' -import { propertyToDelete, updateBulkProperties } from '../../commands/set-property-command' import { showToastCommand } from '../../commands/show-toast-command' import { updateFunctionCommand } from '../../commands/update-function-command' import { updateSelectedViews } from '../../commands/update-selected-views-command' @@ -289,13 +287,6 @@ export function staticReparentAndUpdatePosition( target.parentPath.intendedParentPath, ) - const isGrid = MetadataUtils.isGridLayoutedContainer( - MetadataUtils.findElementByElementPath( - editorStateContext.startingMetadata, - target.parentPath.intendedParentPath, - ), - ) - const commands = elementsToInsert.flatMap((elementToInsert) => { return [ updateFunctionCommand('always', (editor, commandLifecycle) => { @@ -327,17 +318,7 @@ export function staticReparentAndUpdatePosition( ) function getAbsolutePositioningCommands(targetPath: ElementPath): Array { - if (isGrid) { - return [ - updateBulkProperties('always', targetPath, [ - propertyToDelete(PP.create('style', 'position')), - propertyToDelete(PP.create('style', 'top')), - propertyToDelete(PP.create('style', 'left')), - propertyToDelete(PP.create('style', 'bottom')), - propertyToDelete(PP.create('style', 'right')), - ]), - ] - } else if (strategy === 'REPARENT_AS_ABSOLUTE') { + if (strategy === 'REPARENT_AS_ABSOLUTE') { return positionElementToCoordinatesCommands( { oldPath: elementToInsert.elementPath, newPath: targetPath }, pasteContext.originalAllElementProps, diff --git a/editor/src/components/canvas/canvas-strategies/strategies/absolute-reparent-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/absolute-reparent-strategy.tsx index c44f772ae056..87771a51e3f9 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/absolute-reparent-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/absolute-reparent-strategy.tsx @@ -35,10 +35,7 @@ import { import type { InteractionSession, UpdatedPathMap } from '../interaction-state' import { absoluteMoveStrategy } from './absolute-move-strategy' import { honoursPropsPosition, shouldKeepMovingDraggedGroupChildren } from './absolute-utils' -import { - replaceFragmentLikePathsWithTheirChildrenRecursive, - treatElementAsFragmentLike, -} from './fragment-like-helpers' +import { replaceFragmentLikePathsWithTheirChildrenRecursive } from './fragment-like-helpers' import { ifAllowedToReparent, isAllowedToReparent } from './reparent-helpers/reparent-helpers' import type { ForcePins } from './reparent-helpers/reparent-property-changes' import { getAbsoluteReparentPropertyChanges } from './reparent-helpers/reparent-property-changes' diff --git a/editor/src/components/canvas/canvas-strategies/strategies/drag-to-insert-metastrategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/drag-to-insert-metastrategy.tsx index 425afc32cdb4..04da545d72f5 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/drag-to-insert-metastrategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/drag-to-insert-metastrategy.tsx @@ -47,6 +47,7 @@ import { targetPaths, } from '../canvas-strategy-types' import type { InteractionSession } from '../interaction-state' +import type { ParentDisplayType } from './reparent-metastrategy' import { getApplicableReparentFactories } from './reparent-metastrategy' import type { ReparentStrategy } from './reparent-helpers/reparent-strategy-helpers' import { styleStringInArray } from '../../../../utils/common-constants' @@ -116,9 +117,11 @@ export const dragToInsertMetaStrategy: MetaCanvasStrategy = ( function getDragToInsertStrategyName( strategyType: ReparentStrategy, - parentDisplayType: 'flex' | 'flow', + parentDisplayType: ParentDisplayType, ): string { switch (strategyType) { + case 'REPARENT_INTO_GRID': + return 'Drag to Insert (Grid)' case 'REPARENT_AS_ABSOLUTE': return 'Drag to Insert (Abs)' case 'REPARENT_AS_STATIC': diff --git a/editor/src/components/canvas/canvas-strategies/strategies/draw-to-insert-metastrategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/draw-to-insert-metastrategy.tsx index 1f57ba6d35af..b3139f883941 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/draw-to-insert-metastrategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/draw-to-insert-metastrategy.tsx @@ -9,7 +9,6 @@ import { isImg } from '../../../../core/model/project-file-utils' import { mapDropNulls, stripNulls } from '../../../../core/shared/array-utils' import { foldEither } from '../../../../core/shared/either' import * as EP from '../../../../core/shared/element-path' -import { elementPath } from '../../../../core/shared/element-path' import type { ElementInstanceMetadataMap, JSXAttributes, @@ -56,6 +55,7 @@ import { } from '../canvas-strategy-types' import type { InteractionSession } from '../interaction-state' import { boundingArea } from '../interaction-state' +import type { ParentDisplayType } from './reparent-metastrategy' import { getApplicableReparentFactories } from './reparent-metastrategy' import type { ReparentStrategy } from './reparent-helpers/reparent-strategy-helpers' import { styleStringInArray } from '../../../../utils/common-constants' @@ -68,6 +68,7 @@ import { wildcardPatch } from '../../commands/wildcard-patch-command' import type { InsertionPath } from '../../../editor/store/insertion-path' import { childInsertionPath } from '../../../editor/store/insertion-path' import { gridDrawToInsertStrategy } from './grid-draw-to-insert-strategy' +import { assertNever } from '../../../../core/shared/utils' /** * @@ -141,7 +142,7 @@ export const drawToInsertMetaStrategy: MetaCanvasStrategy = ( export function getDrawToInsertStrategyName( strategyType: ReparentStrategy, - parentDisplayType: 'flex' | 'flow', + parentDisplayType: ParentDisplayType, ): string { switch (strategyType) { case 'REPARENT_AS_ABSOLUTE': @@ -149,9 +150,15 @@ export function getDrawToInsertStrategyName( case 'REPARENT_AS_STATIC': if (parentDisplayType === 'flex') { return 'Draw to Insert (Flex)' + } else if (parentDisplayType === 'grid') { + return 'Draw to Insert (Grid)' } else { return 'Draw to Insert (Flow)' } + case 'REPARENT_INTO_GRID': + return 'Draw to Insert (Grid)' + default: + assertNever(strategyType) } } 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 new file mode 100644 index 000000000000..29b1ffb84615 --- /dev/null +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-reparent-strategy.tsx @@ -0,0 +1,248 @@ +import { MetadataUtils } from '../../../../core/model/element-metadata-utils' +import { mapDropNulls } from '../../../../core/shared/array-utils' +import * as EP from '../../../../core/shared/element-path' +import * as PP from '../../../../core/shared/property-path' +import type { ElementPath } from '../../../../core/shared/project-file-types' +import { CSSCursor } from '../../canvas-types' +import { setCursorCommand } from '../../commands/set-cursor-command' +import { + propertyToDelete, + propertyToSet, + updateBulkProperties, +} from '../../commands/set-property-command' +import { updateSelectedViews } from '../../commands/update-selected-views-command' +import { ParentBounds } from '../../controls/parent-bounds' +import { ParentOutlines } from '../../controls/parent-outlines' +import { ZeroSizedElementControls } from '../../controls/zero-sized-element-controls' +import type { CanvasStrategyFactory } from '../canvas-strategies' +import type { + CanvasStrategy, + CustomStrategyState, + InteractionCanvasState, +} from '../canvas-strategy-types' +import { + controlWithProps, + emptyStrategyApplicationResult, + getTargetPathsFromInteractionTarget, + strategyApplicationResult, +} from '../canvas-strategy-types' +import type { InteractionSession, UpdatedPathMap } from '../interaction-state' +import { honoursPropsPosition, shouldKeepMovingDraggedGroupChildren } from './absolute-utils' +import { replaceFragmentLikePathsWithTheirChildrenRecursive } from './fragment-like-helpers' +import { ifAllowedToReparent, isAllowedToReparent } from './reparent-helpers/reparent-helpers' +import type { ReparentTarget } from './reparent-helpers/reparent-strategy-helpers' +import { getReparentOutcome, pathToReparent } from './reparent-utils' +import { flattenSelection } from './shared-move-strategies-helpers' +import { isInfinityRectangle } from '../../../../core/shared/math-utils' +import { showGridControls } from '../../commands/show-grid-controls-command' +import { GridControls } from '../../controls/grid-controls' +import type { ElementInstanceMetadataMap } from '../../../../core/shared/element-template' +import type { ElementPathTrees } from '../../../../core/shared/element-path-tree' +import type { AllElementProps } from '../../../editor/store/editor-state' +import type { BuiltInDependencies } from '../../../../core/es-modules/package-manager/built-in-dependencies-list' +import type { NodeModules, ProjectContentTreeRoot } from 'utopia-shared/src/types' +import type { InsertionPath } from '../../../editor/store/insertion-path' +import type { WhenToRun } from '../../commands/commands' +import { removeAbsolutePositioningProps } from './reparent-helpers/reparent-property-changes' + +export function gridReparentStrategy( + reparentTarget: ReparentTarget, + fitness: number, + customStrategyState: CustomStrategyState, +): CanvasStrategyFactory { + return ( + canvasState: InteractionCanvasState, + interactionSession: InteractionSession | null, + ): CanvasStrategy | null => { + const selectedElements = getTargetPathsFromInteractionTarget(canvasState.interactionTarget) + if ( + selectedElements.length === 0 || + interactionSession == null || + interactionSession.interactionData.type !== 'DRAG' + ) { + return null + } + + const gridFrame = MetadataUtils.findElementByElementPath( + canvasState.startingMetadata, + reparentTarget.newParent.intendedParentPath, + )?.globalFrame + if (gridFrame == null || isInfinityRectangle(gridFrame)) { + return null + } + + const dragInteractionData = interactionSession.interactionData + const filteredSelectedElements = flattenSelection(selectedElements) + const isApplicable = replaceFragmentLikePathsWithTheirChildrenRecursive( + canvasState.startingMetadata, + canvasState.startingAllElementProps, + canvasState.startingElementPathTree, + filteredSelectedElements, + ).every((element) => { + return honoursPropsPosition(canvasState, element) + }) + if (!isApplicable) { + return null + } + return { + id: `GRID_REPARENT`, + name: `Reparent (Grid)`, + descriptiveLabel: 'Reparent (Grid)', + icon: { + category: 'modalities', + type: 'reparent-large', + }, + controlsToRender: [ + controlWithProps({ + control: ParentOutlines, + props: { targetParent: reparentTarget.newParent.intendedParentPath }, + key: 'parent-outlines-control', + show: 'visible-only-while-active', + }), + controlWithProps({ + control: ParentBounds, + props: { targetParent: reparentTarget.newParent.intendedParentPath }, + key: 'parent-bounds-control', + show: 'visible-only-while-active', + }), + controlWithProps({ + control: ZeroSizedElementControls, + props: { showAllPossibleElements: true }, + key: 'zero-size-control', + show: 'visible-only-while-active', + }), + { + control: GridControls, + props: { targets: [reparentTarget.newParent.intendedParentPath] }, + key: `draw-into-grid-strategy-controls`, + show: 'always-visible', + priority: 'bottom', + }, + ], + fitness: shouldKeepMovingDraggedGroupChildren( + canvasState.startingMetadata, + selectedElements, + reparentTarget.newParent, + ) + ? 1 + : fitness, + apply: () => { + const { projectContents, nodeModules } = canvasState + const newParent = reparentTarget.newParent + return ifAllowedToReparent( + canvasState, + canvasState.startingMetadata, + filteredSelectedElements, + newParent.intendedParentPath, + () => { + if (dragInteractionData.drag == null) { + return emptyStrategyApplicationResult + } + + const allowedToReparent = filteredSelectedElements.every((selectedElement) => { + return isAllowedToReparent( + canvasState.projectContents, + canvasState.startingMetadata, + selectedElement, + newParent.intendedParentPath, + ) + }) + + if (!(reparentTarget.shouldReparent && allowedToReparent)) { + return emptyStrategyApplicationResult + } + const outcomes = mapDropNulls( + (selectedElement) => + gridReparentCommands( + canvasState.startingMetadata, + canvasState.startingElementPathTree, + canvasState.startingAllElementProps, + canvasState.builtInDependencies, + projectContents, + nodeModules, + selectedElement, + newParent, + ), + selectedElements, + ) + + let newPaths: Array = [] + let updatedTargetPaths: UpdatedPathMap = {} + + outcomes.forEach((c) => { + newPaths.push(c.newPath) + updatedTargetPaths[EP.toString(c.oldPath)] = c.newPath + }) + + const gridContainerCommands = updateBulkProperties( + 'mid-interaction', + reparentTarget.newParent.intendedParentPath, + [ + propertyToSet(PP.create('style', 'width'), gridFrame.width), + propertyToSet(PP.create('style', 'height'), gridFrame.height), + ], + ) + + const elementsToRerender = EP.uniqueElementPaths([ + ...customStrategyState.elementsToRerender, + ...newPaths, + ...newPaths.map(EP.parentPath), + ...filteredSelectedElements.map(EP.parentPath), + ]) + + return strategyApplicationResult( + [ + ...outcomes.flatMap((c) => c.commands), + gridContainerCommands, + updateSelectedViews('always', newPaths), + setCursorCommand(CSSCursor.Reparent), + showGridControls('mid-interaction', reparentTarget.newParent.intendedParentPath), + ], + { + elementsToRerender, + }, + ) + }, + ) + }, + } + } +} + +function gridReparentCommands( + jsxMetadata: ElementInstanceMetadataMap, + tree: ElementPathTrees, + allElementProps: AllElementProps, + builtinDependencies: BuiltInDependencies, + projectContents: ProjectContentTreeRoot, + nodeModules: NodeModules, + target: ElementPath, + newParent: InsertionPath, +) { + const reparentResult = getReparentOutcome( + jsxMetadata, + tree, + allElementProps, + builtinDependencies, + projectContents, + nodeModules, + pathToReparent(target), + newParent, + 'always', + null, + ) + + if (reparentResult == null) { + return null + } + + const { commands: reparentCommands, newPath } = reparentResult + + const gridCellCommands = removeAbsolutePositioningProps('always', newPath) + + return { + commands: [...reparentCommands, gridCellCommands], + newPath: newPath, + oldPath: target, + } +} diff --git a/editor/src/components/canvas/canvas-strategies/strategies/reparent-helpers/reparent-property-changes.ts b/editor/src/components/canvas/canvas-strategies/strategies/reparent-helpers/reparent-property-changes.ts index 3922ab30a29d..86a6f01ccc2d 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/reparent-helpers/reparent-property-changes.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/reparent-helpers/reparent-property-changes.ts @@ -35,11 +35,15 @@ import { adjustCssLengthProperties, lengthPropertyToAdjust, } from '../../../commands/adjust-css-length-command' -import type { CanvasCommand } from '../../../commands/commands' +import type { CanvasCommand, WhenToRun } from '../../../commands/commands' import type { ConvertCssPercentToPx } from '../../../commands/convert-css-percent-to-px-command' import { convertCssPercentToPx } from '../../../commands/convert-css-percent-to-px-command' import { deleteProperties } from '../../../commands/delete-properties-command' -import { setProperty } from '../../../commands/set-property-command' +import { + propertyToDelete, + setProperty, + updateBulkProperties, +} from '../../../commands/set-property-command' import { getOptionalCommandToConvertDisplayInlineBlock, singleAxisAutoLayoutContainerDirections, @@ -404,7 +408,19 @@ export function getReparentPropertyChanges( return [...basicCommads, ...strategyCommands] } + case 'REPARENT_INTO_GRID': + return [removeAbsolutePositioningProps('always', target)] default: assertNever(reparentStrategy) } } + +export function removeAbsolutePositioningProps(whenToRun: WhenToRun, path: ElementPath) { + return updateBulkProperties(whenToRun, path, [ + propertyToDelete(PP.create('style', 'position')), + propertyToDelete(PP.create('style', 'top')), + propertyToDelete(PP.create('style', 'left')), + propertyToDelete(PP.create('style', 'bottom')), + propertyToDelete(PP.create('style', 'right')), + ]) +} diff --git a/editor/src/components/canvas/canvas-strategies/strategies/reparent-helpers/reparent-strategy-helpers.ts b/editor/src/components/canvas/canvas-strategies/strategies/reparent-helpers/reparent-strategy-helpers.ts index 3184a5e6e90f..ae3464ccdc13 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/reparent-helpers/reparent-strategy-helpers.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/reparent-helpers/reparent-strategy-helpers.ts @@ -16,12 +16,12 @@ import type { AllElementProps } from '../../../../editor/store/editor-state' import type { InsertionPath } from '../../../../editor/store/insertion-path' import type { ElementPathTrees } from '../../../../../core/shared/element-path-tree' import { assertNever } from '../../../../../core/shared/utils' -import { PropertyControlsInfo } from '../../../../custom-code/code-file' import * as EP from '../../../../../core/shared/element-path' export type ReparentAsAbsolute = 'REPARENT_AS_ABSOLUTE' export type ReparentAsStatic = 'REPARENT_AS_STATIC' -export type ReparentStrategy = ReparentAsAbsolute | ReparentAsStatic +export type ReparentIntoGrid = 'REPARENT_INTO_GRID' +export type ReparentStrategy = ReparentAsAbsolute | ReparentAsStatic | ReparentIntoGrid export type FindReparentStrategyResult = { strategy: ReparentStrategy diff --git a/editor/src/components/canvas/canvas-strategies/strategies/reparent-helpers/reparent-strategy-parent-lookup.ts b/editor/src/components/canvas/canvas-strategies/strategies/reparent-helpers/reparent-strategy-parent-lookup.ts index 8d2d6d1229b2..ee790b68a604 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/reparent-helpers/reparent-strategy-parent-lookup.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/reparent-helpers/reparent-strategy-parent-lookup.ts @@ -466,7 +466,16 @@ function findParentUnderPointByArea( const targetParentUnderPoint: ReparentTarget = (() => { const insertionPath = getInsertionPathForReparentTarget(targetParentPath, metadata) - if (shouldReparentAsAbsoluteOrStatic === 'REPARENT_AS_ABSOLUTE') { + if (shouldReparentAsAbsoluteOrStatic === 'REPARENT_INTO_GRID') { + return { + shouldReparent: true, + newParent: insertionPath, + shouldShowPositionIndicator: false, + newIndex: -1, + shouldConvertToInline: 'do-not-convert', + defaultReparentType: 'REPARENT_INTO_GRID', + } + } else if (shouldReparentAsAbsoluteOrStatic === 'REPARENT_AS_ABSOLUTE') { // TODO we now assume this is "absolute", but this is too vauge return { shouldReparent: true, @@ -567,10 +576,12 @@ export function autoLayoutParentAbsoluteOrStatic( return 'REPARENT_AS_ABSOLUTE' } - const parentIsFlexLayout = - MetadataUtils.findLayoutSystemForChildren(metadata, pathTrees, parent) === 'flex' - if (parentIsFlexLayout) { + const parentLayout = MetadataUtils.findLayoutSystemForChildren(metadata, pathTrees, parent) + + if (parentLayout === 'flex') { return 'REPARENT_AS_STATIC' + } else if (parentLayout === 'grid') { + return 'REPARENT_INTO_GRID' } const isTextFromMetadata = MetadataUtils.isTextFromMetadata( diff --git a/editor/src/components/canvas/canvas-strategies/strategies/reparent-metastrategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/reparent-metastrategy.tsx index 5b6a8876c7a3..a5d79e59521d 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/reparent-metastrategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/reparent-metastrategy.tsx @@ -32,13 +32,15 @@ import { flattenSelection } from './shared-move-strategies-helpers' import type { InsertionPath } from '../../../editor/store/insertion-path' import { childInsertionPath } from '../../../editor/store/insertion-path' import { treatElementAsGroupLike } from './group-helpers' -import { PropertyControlsInfo } from '../../../custom-code/code-file' +import { gridReparentStrategy } from './grid-reparent-strategy' + +export type ParentDisplayType = 'flex' | 'flow' | 'grid' interface ReparentFactoryAndDetails { targetParent: InsertionPath targetIndex: number | null strategyType: ReparentStrategy // FIXME horrible name - targetParentDisplayType: 'flex' | 'flow' // should this be here? + targetParentDisplayType: ParentDisplayType // should this be here? fitness: number dragType: 'absolute' | 'static' factory: CanvasStrategyFactory @@ -66,83 +68,108 @@ export function getApplicableReparentFactories( elementSupportsChildren, ) - const factories: Array = reparentStrategies.map((result) => { - switch (result.strategy) { - case 'REPARENT_AS_ABSOLUTE': { - const fitness = (() => { - if (!cmdPressed) { - return 0 - } - if (result.isReparentingOutFromScene) { - return OutOfSceneReparentWeight - } - if (result.isFallback) { - return FallbackReparentWeight - } - return DefaultReparentWeight - })() - - if (allDraggedElementsAbsolute) { + const factories: Array = reparentStrategies.map( + (result): ReparentFactoryAndDetails => { + switch (result.strategy) { + case 'REPARENT_INTO_GRID': { + const fitness = (() => { + if (!cmdPressed) { + return 0 + } + if (result.isReparentingOutFromScene) { + return OutOfSceneReparentWeight + } + if (result.isFallback) { + return FallbackReparentWeight + } + return DefaultReparentWeight + })() return { targetParent: result.target.newParent, targetIndex: null, strategyType: result.strategy, - targetParentDisplayType: 'flow', + targetParentDisplayType: 'grid', fitness: fitness, dragType: 'absolute', - factory: baseAbsoluteReparentStrategy(result.target, fitness, customStrategyState), + factory: gridReparentStrategy(result.target, fitness, customStrategyState), + } + } + case 'REPARENT_AS_ABSOLUTE': { + const fitness = (() => { + if (!cmdPressed) { + return 0 + } + if (result.isReparentingOutFromScene) { + return OutOfSceneReparentWeight + } + if (result.isFallback) { + return FallbackReparentWeight + } + return DefaultReparentWeight + })() + + if (allDraggedElementsAbsolute) { + return { + targetParent: result.target.newParent, + targetIndex: null, + strategyType: result.strategy, + targetParentDisplayType: 'flow', + fitness: fitness, + dragType: 'absolute', + factory: baseAbsoluteReparentStrategy(result.target, fitness, customStrategyState), + } + } else { + return { + targetParent: result.target.newParent, + targetIndex: null, + strategyType: result.strategy, + targetParentDisplayType: 'flow', + fitness: fitness, + dragType: 'absolute', + factory: baseFlexReparentToAbsoluteStrategy(result.target, fitness), + } } - } else { + } + case 'REPARENT_AS_STATIC': { + const parentLayoutSystem = MetadataUtils.findLayoutSystemForChildren( + canvasState.startingMetadata, + canvasState.startingElementPathTree, + result.target.newParent.intendedParentPath, + ) + const targetParentDisplayType = parentLayoutSystem === 'flex' ? 'flex' : 'flow' + + // We likely never want flow insertion or re-parenting to be the default + const fitness = (() => { + if (!cmdPressed) { + return 0 + } + if (result.isReparentingOutFromScene) { + return OutOfSceneReparentWeight + } + if (targetParentDisplayType === 'flow') { + return FlowReparentWeight + } + if (result.isFallback) { + return FallbackReparentWeight + } + return DefaultReparentWeight + })() + return { targetParent: result.target.newParent, - targetIndex: null, + targetIndex: result.target.newIndex, strategyType: result.strategy, - targetParentDisplayType: 'flow', + targetParentDisplayType: targetParentDisplayType, fitness: fitness, - dragType: 'absolute', - factory: baseFlexReparentToAbsoluteStrategy(result.target, fitness), - } - } - } - case 'REPARENT_AS_STATIC': { - const parentLayoutSystem = MetadataUtils.findLayoutSystemForChildren( - canvasState.startingMetadata, - canvasState.startingElementPathTree, - result.target.newParent.intendedParentPath, - ) - const targetParentDisplayType = parentLayoutSystem === 'flex' ? 'flex' : 'flow' - - // We likely never want flow insertion or re-parenting to be the default - const fitness = (() => { - if (!cmdPressed) { - return 0 + dragType: 'static', + factory: baseReparentAsStaticStrategy(result.target, fitness, targetParentDisplayType), } - if (result.isReparentingOutFromScene) { - return OutOfSceneReparentWeight - } - if (targetParentDisplayType === 'flow') { - return FlowReparentWeight - } - if (result.isFallback) { - return FallbackReparentWeight - } - return DefaultReparentWeight - })() - - return { - targetParent: result.target.newParent, - targetIndex: result.target.newIndex, - strategyType: result.strategy, - targetParentDisplayType: targetParentDisplayType, - fitness: fitness, - dragType: 'static', - factory: baseReparentAsStaticStrategy(result.target, fitness, targetParentDisplayType), } + default: + assertNever(result.strategy) } - default: - assertNever(result.strategy) - } - }) + }, + ) return factories } diff --git a/editor/src/components/editor/wrap-in-callbacks.ts b/editor/src/components/editor/wrap-in-callbacks.ts index c7bf3e5d6f14..6350db052d4b 100644 --- a/editor/src/components/editor/wrap-in-callbacks.ts +++ b/editor/src/components/editor/wrap-in-callbacks.ts @@ -229,6 +229,8 @@ function getWrapperStyle( top: desiredFrame.y, left: desiredFrame.x, } + case 'REPARENT_INTO_GRID': + return style default: assertNever(parentType) }