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)
}