From ef1cac238efa7a058ba6f9a30b846fe97b81ed14 Mon Sep 17 00:00:00 2001 From: Balint Gabor <127662+gbalint@users.noreply.github.com> Date: Wed, 11 Oct 2023 12:08:43 +0200 Subject: [PATCH 01/10] Two kinds of convert to absolute and move strategies --- .../canvas-strategies/canvas-strategies.tsx | 6 +- ...solute-and-move-strategy.spec.browser2.tsx | 2 +- .../convert-to-absolute-and-move-strategy.tsx | 267 ++++++++++-------- .../flex-reparent-to-absolute-strategy.tsx | 1 + .../src/components/editor/actions/actions.tsx | 1 + 5 files changed, 160 insertions(+), 117 deletions(-) diff --git a/editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx b/editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx index 8f2944c1a30c..77647c2b02f8 100644 --- a/editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx +++ b/editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx @@ -32,7 +32,10 @@ import { isNotYetStartedDragInteraction } from './interaction-state' import { keyboardAbsoluteMoveStrategy } from './strategies/keyboard-absolute-move-strategy' import { absoluteResizeBoundingBoxStrategy } from './strategies/absolute-resize-bounding-box-strategy' import { keyboardAbsoluteResizeStrategy } from './strategies/keyboard-absolute-resize-strategy' -import { convertToAbsoluteAndMoveStrategy } from './strategies/convert-to-absolute-and-move-strategy' +import { + convertToAbsoluteAndMoveAndSetParentFixedStrategy, + convertToAbsoluteAndMoveStrategy, +} from './strategies/convert-to-absolute-and-move-strategy' import { absoluteDuplicateStrategy } from './strategies/absolute-duplicate-strategy' import type { BuiltInDependencies } from '../../../core/es-modules/package-manager/built-in-dependencies-list' import type { StateSelector } from 'zustand' @@ -85,6 +88,7 @@ const moveOrReorderStrategies: MetaCanvasStrategy = ( keyboardAbsoluteMoveStrategy, keyboardReorderStrategy, convertToAbsoluteAndMoveStrategy, + convertToAbsoluteAndMoveAndSetParentFixedStrategy, reorderSliderStategy, ], ) diff --git a/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.spec.browser2.tsx index 74801db95543..2f59fe36e423 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.spec.browser2.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.spec.browser2.tsx @@ -42,7 +42,6 @@ import { pressKey, } from '../../event-helpers.test-utils' import { cmdModifier } from '../../../../utils/modifiers' -import { ConvertToAbsoluteAndMoveStrategyID } from './convert-to-absolute-and-move-strategy' import type { FragmentLikeType } from './fragment-like-helpers' import { AllFragmentLikeNonDomElementTypes, @@ -54,6 +53,7 @@ import { getOpeningFragmentLikeTag, } from './fragment-like-helpers.test-utils' import { selectComponentsForTest } from '../../../../utils/utils.test-utils' +import { ConvertToAbsoluteAndMoveStrategyID } from './convert-to-absolute-and-move-strategy' const complexProject = () => { const code = ` diff --git a/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.tsx index 8115be3ef4d8..cd85ea830f5f 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.tsx @@ -37,7 +37,7 @@ import { zeroCanvasRect, } from '../../../../core/shared/math-utils' import type { ElementPath } from '../../../../core/shared/project-file-types' -import { fastForEach } from '../../../../core/shared/utils' +import { assertNever, fastForEach } from '../../../../core/shared/utils' import { getJSXElementFromProjectContents } from '../../../editor/store/editor-state' import type { FullFrame } from '../../../frame' import { getFullFrame } from '../../../frame' @@ -80,132 +80,165 @@ import { cssPixelLength } from '../../../inspector/common/css-utils' import type { ProjectContentTreeRoot } from '../../../assets' import { showToastCommand } from '../../commands/show-toast-command' +type SetParentToFixed = 'set-parent-to-fixed' | 'dont-set-parent-to-fixed' + +export const ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID = + 'CONVERT_TO_ABSOLUTE_AND_MOVE_AND_SET_PARENT_FIXED_STRATEGY' export const ConvertToAbsoluteAndMoveStrategyID = 'CONVERT_TO_ABSOLUTE_AND_MOVE_STRATEGY' -export function convertToAbsoluteAndMoveStrategy( - canvasState: InteractionCanvasState, - interactionSession: InteractionSession | null, -): CanvasStrategy | null { - const originalTargets = retargetStrategyToTopMostFragmentLikeElement(canvasState) // this needs a better variable name - const retargetedTargets = retargetStrategyToChildrenOfFragmentLikeElements(canvasState) +export const convertToAbsoluteAndMoveStrategy = convertToAbsoluteAndMoveStrategyFactory( + 'dont-set-parent-to-fixed', +) - if ( - !retargetedTargets.every((element) => { - const elementMetadata = MetadataUtils.findElementByElementPath( - canvasState.startingMetadata, - element, - ) - return ( - elementMetadata?.specialSizeMeasurements.position !== 'absolute' && - honoursPropsPosition(canvasState, element) - ) - }) - ) { - return null +export const convertToAbsoluteAndMoveAndSetParentFixedStrategy = + convertToAbsoluteAndMoveStrategyFactory('set-parent-to-fixed') + +function getBasicStrategyProperties(setParentToFixed: SetParentToFixed) { + switch (setParentToFixed) { + case 'set-parent-to-fixed': + return { + id: ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID, + name: 'Move (Abs + Set Parent Fixed)', + descriptiveLabel: 'Converting To Absolute And Moving (setting parent to fixed)', + } + case 'dont-set-parent-to-fixed': + return { + id: ConvertToAbsoluteAndMoveStrategyID, + name: 'Move (Abs)', + descriptiveLabel: 'Converting To Absolute And Moving', + } + default: + assertNever(setParentToFixed) } +} - const autoLayoutSiblings = getAutoLayoutSiblings( - canvasState.startingMetadata, - canvasState.startingElementPathTree, - originalTargets, - ) - const hasAutoLayoutSiblings = autoLayoutSiblings.length > 1 - const autoLayoutSiblingsBounds = getAutoLayoutSiblingsBounds( - canvasState.startingMetadata, - canvasState.startingElementPathTree, - originalTargets, - ) +function convertToAbsoluteAndMoveStrategyFactory(setParentToFixed: SetParentToFixed) { + return ( + canvasState: InteractionCanvasState, + interactionSession: InteractionSession | null, + ): CanvasStrategy | null => { + const originalTargets = retargetStrategyToTopMostFragmentLikeElement(canvasState) // this needs a better variable name + const retargetedTargets = retargetStrategyToChildrenOfFragmentLikeElements(canvasState) - const autoLayoutSiblingsControl = hasAutoLayoutSiblings - ? [ - controlWithProps({ - control: AutoLayoutSiblingsOutline, - props: { bounds: autoLayoutSiblingsBounds }, - key: 'autolayout-siblings-outline', - show: 'always-visible', - }), - ] - : [] - - return { - id: ConvertToAbsoluteAndMoveStrategyID, - name: 'Move (Abs)', - descriptiveLabel: 'Converting To Absolute And Moving', - icon: { - category: 'modalities', - type: 'moveabs-large', - }, - controlsToRender: [ - controlWithProps({ - control: ImmediateParentOutlines, - props: { targets: originalTargets }, - key: 'parent-outlines-control', - show: 'visible-only-while-active', - }), - controlWithProps({ - control: ImmediateParentBounds, - props: { targets: originalTargets }, - key: 'parent-bounds-control', - show: 'visible-only-while-active', - }), - ...autoLayoutSiblingsControl, - ], // Uses existing hooks in select-mode-hooks.tsx - fitness: getFitness( + if ( + !retargetedTargets.every((element) => { + const elementMetadata = MetadataUtils.findElementByElementPath( + canvasState.startingMetadata, + element, + ) + return ( + elementMetadata?.specialSizeMeasurements.position !== 'absolute' && + honoursPropsPosition(canvasState, element) + ) + }) + ) { + return null + } + + const autoLayoutSiblings = getAutoLayoutSiblings( + canvasState.startingMetadata, + canvasState.startingElementPathTree, + originalTargets, + ) + const hasAutoLayoutSiblings = autoLayoutSiblings.length > 1 + const autoLayoutSiblingsBounds = getAutoLayoutSiblingsBounds( + canvasState.startingMetadata, + canvasState.startingElementPathTree, + originalTargets, + ) + + const autoLayoutSiblingsControl = hasAutoLayoutSiblings + ? [ + controlWithProps({ + control: AutoLayoutSiblingsOutline, + props: { bounds: autoLayoutSiblingsBounds }, + key: 'autolayout-siblings-outline', + show: 'always-visible', + }), + ] + : [] + + const baseFitness = getFitness( interactionSession, hasAutoLayoutSiblings, autoLayoutSiblingsBounds, originalTargets.length > 1, - ), - apply: () => { - if ( - interactionSession != null && - interactionSession.interactionData.type === 'DRAG' && - interactionSession.interactionData.drag != null - ) { - const getConversionAndMoveCommands = ( - snappedDragVector: CanvasPoint, - ): { - commands: Array - intendedBounds: Array - } => { - return getEscapeHatchCommands( - getTargetPathsFromInteractionTarget(canvasState.interactionTarget), - canvasState.startingMetadata, + ) + + return { + ...getBasicStrategyProperties(setParentToFixed), + icon: { + category: 'modalities', + type: 'moveabs-large', + }, + controlsToRender: [ + controlWithProps({ + control: ImmediateParentOutlines, + props: { targets: originalTargets }, + key: 'parent-outlines-control', + show: 'visible-only-while-active', + }), + controlWithProps({ + control: ImmediateParentBounds, + props: { targets: originalTargets }, + key: 'parent-bounds-control', + show: 'visible-only-while-active', + }), + ...autoLayoutSiblingsControl, + ], // Uses existing hooks in select-mode-hooks.tsx + fitness: setParentToFixed === 'set-parent-to-fixed' ? baseFitness : baseFitness - 0.1, + apply: () => { + if ( + interactionSession != null && + interactionSession.interactionData.type === 'DRAG' && + interactionSession.interactionData.drag != null + ) { + const getConversionAndMoveCommands = ( + snappedDragVector: CanvasPoint, + ): { + commands: Array + intendedBounds: Array + } => { + return getEscapeHatchCommands( + getTargetPathsFromInteractionTarget(canvasState.interactionTarget), + canvasState.startingMetadata, + canvasState, + snappedDragVector, + setParentToFixed, + ) + } + const absoluteMoveApplyResult = applyMoveCommon( + retargetedTargets, + getTargetPathsFromInteractionTarget(canvasState.interactionTarget), // TODO eventually make this handle fragmentLike elements canvasState, - snappedDragVector, + interactionSession, + getConversionAndMoveCommands, ) - } - const absoluteMoveApplyResult = applyMoveCommon( - retargetedTargets, - getTargetPathsFromInteractionTarget(canvasState.interactionTarget), // TODO eventually make this handle fragmentLike elements - canvasState, - interactionSession, - getConversionAndMoveCommands, - ) - const strategyIndicatorCommand = wildcardPatch('mid-interaction', { - canvas: { - controls: { - dragToMoveIndicatorFlags: { - $set: { - showIndicator: true, - dragType: 'absolute', - reparent: 'none', - ancestor: false, + const strategyIndicatorCommand = wildcardPatch('mid-interaction', { + canvas: { + controls: { + dragToMoveIndicatorFlags: { + $set: { + showIndicator: true, + dragType: 'absolute', + reparent: 'none', + ancestor: false, + }, }, }, }, - }, - }) + }) - return strategyApplicationResult([ - ...absoluteMoveApplyResult.commands, - strategyIndicatorCommand, - ]) - } - // Fallback for when the checks above are not satisfied. - return emptyStrategyApplicationResult - }, + return strategyApplicationResult([ + ...absoluteMoveApplyResult.commands, + strategyIndicatorCommand, + ]) + } + // Fallback for when the checks above are not satisfied. + return emptyStrategyApplicationResult + }, + } } } @@ -289,6 +322,7 @@ export function getEscapeHatchCommands( metadata: ElementInstanceMetadataMap, canvasState: InteractionCanvasState, dragDelta: CanvasVector | null, + setParentToFixed: SetParentToFixed, ): { commands: Array intendedBounds: Array @@ -314,11 +348,14 @@ export function getEscapeHatchCommands( sortedElements, ) - const setParentsToFixedSizeCommands = createSetParentsToFixedSizeCommands( - elementsToConvertToAbsolute, - metadata, - canvasState.projectContents, - ) + const setParentsToFixedSizeCommands = + setParentToFixed === 'set-parent-to-fixed' + ? createSetParentsToFixedSizeCommands( + elementsToConvertToAbsolute, + metadata, + canvasState.projectContents, + ) + : [] /** * It's possible to have descendants where the layout is defined by an ancestor diff --git a/editor/src/components/canvas/canvas-strategies/strategies/flex-reparent-to-absolute-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/flex-reparent-to-absolute-strategy.tsx index 16bcf25ffb40..7ab5d8446dc1 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/flex-reparent-to-absolute-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/flex-reparent-to-absolute-strategy.tsx @@ -97,6 +97,7 @@ export function baseFlexReparentToAbsoluteStrategy( canvasState.startingMetadata, canvasState, canvasPoint({ x: 0, y: 0 }), + 'dont-set-parent-to-fixed', ).commands return strategyApplicationResult( diff --git a/editor/src/components/editor/actions/actions.tsx b/editor/src/components/editor/actions/actions.tsx index 10e89f372a4c..617ca68c4ea1 100644 --- a/editor/src/components/editor/actions/actions.tsx +++ b/editor/src/components/editor/actions/actions.tsx @@ -5057,6 +5057,7 @@ export const UPDATE_FNS = { editor.jsxMetadata, canvasState, null, + 'set-parent-to-fixed', ).commands return foldAndApplyCommandsSimple(editor, commands) } else { From 156db819d0101bcfae76f7000c6f575516d610a7 Mon Sep 17 00:00:00 2001 From: Balint Gabor <127662+gbalint@users.noreply.github.com> Date: Wed, 11 Oct 2023 14:09:40 +0200 Subject: [PATCH 02/10] Fix test --- .../strategies/ancestor-metastrategy.spec.browser2.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/src/components/canvas/canvas-strategies/strategies/ancestor-metastrategy.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/ancestor-metastrategy.spec.browser2.tsx index 806af270ede7..ebe507bcde3c 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/ancestor-metastrategy.spec.browser2.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/ancestor-metastrategy.spec.browser2.tsx @@ -579,7 +579,7 @@ describe('finds an applicable strategy for the nearest ancestor', () => { runTest(codeElementWithSibling, pathForShallowNestedElement, (editor) => { const strategies = editor.getEditorState().strategyState.sortedApplicableStrategies - expect(strategies?.length).toEqual(2) + expect(strategies?.length).toEqual(3) if (strategies == null) { // here for type assertion throw new Error('`strategies` should not be null') From eb8d07618662eb6732b6b0b073532bc07eaf739b Mon Sep 17 00:00:00 2001 From: Balint Gabor <127662+gbalint@users.noreply.github.com> Date: Wed, 11 Oct 2023 15:03:03 +0200 Subject: [PATCH 03/10] Generalize runEscapeHatch and add tests --- ...solute-and-move-strategy.spec.browser2.tsx | 235 +++++++++++++++++- .../convert-to-absolute-and-move-strategy.tsx | 49 ++-- .../flex-reparent-to-absolute-strategy.tsx | 2 +- editor/src/components/context-menu-items.ts | 5 +- editor/src/components/editor/action-types.ts | 2 + .../editor/actions/action-creators.ts | 7 +- .../src/components/editor/actions/actions.tsx | 2 +- .../self-layout-subsection.tsx | 2 +- 8 files changed, 269 insertions(+), 35 deletions(-) diff --git a/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.spec.browser2.tsx index 2f59fe36e423..14360536975f 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.spec.browser2.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.spec.browser2.tsx @@ -54,6 +54,7 @@ import { } from './fragment-like-helpers.test-utils' import { selectComponentsForTest } from '../../../../utils/utils.test-utils' import { ConvertToAbsoluteAndMoveStrategyID } from './convert-to-absolute-and-move-strategy' +import CanvasActions from '../../canvas-actions' const complexProject = () => { const code = ` @@ -524,7 +525,10 @@ describe('Convert to Absolute/runEscapeHatch action', () => { ) // CONVERT TO ABSOLUTE - await renderResult.dispatch([runEscapeHatch(targetsToConvert)], true) + await renderResult.dispatch( + [runEscapeHatch(targetsToConvert, 'set-hugging-parent-to-fixed')], + true, + ) fastForEach(expectedAbsoluteElements, (target) => { const result = MetadataUtils.isPositionAbsolute( @@ -618,7 +622,10 @@ describe('Convert to Absolute', () => { ) const targetToConvert = EP.fromString('sb/scene/app/child') - await renderResult.dispatch([runEscapeHatch([targetToConvert])], true) + await renderResult.dispatch( + [runEscapeHatch([targetToConvert], 'set-hugging-parent-to-fixed')], + true, + ) expect(getPrintedUiJsCode(renderResult.getEditorState())).toEqual( getCodeForTestProject(childTagAfter), @@ -1071,7 +1078,7 @@ describe('Convert to absolute/escape hatch', () => { ) }) - it('Runs the escape hatch strategy', async () => { + it('Runs the escape hatch strategy and sets hugging parent to fixed size', async () => { const initialEditor = await renderTestEditorWithCode( makeTestProjectCodeWithSnippet(` @@ -1091,7 +1098,7 @@ describe('Convert to absolute/escape hatch', () => { await selectComponentsForTest(initialEditor, [targetElement]) - const action = runEscapeHatch([targetElement]) + const action = runEscapeHatch([targetElement], 'set-hugging-parent-to-fixed') await initialEditor.dispatch([action], true) await initialEditor.getDispatchFollowUpActionsFinished() @@ -1115,7 +1122,7 @@ describe('Convert to absolute/escape hatch', () => { ) }) - it('works on a flow element with all pins', async () => { + it('works on a flow element with all pins and sets hugging parent to fixed size', async () => { const initialEditor = await renderTestEditorWithCode( makeTestProjectCodeWithSnippet(` @@ -1179,7 +1186,7 @@ describe('Convert to absolute/escape hatch', () => { ) }) - it('works on a flow element without siblings', async () => { + it('works on a flow element without siblings and sets hugging parent to fixed size', async () => { const initialEditor = await renderTestEditorWithCode( makeTestProjectCodeWithSnippet(` @@ -1235,7 +1242,7 @@ describe('Convert to absolute/escape hatch', () => { ) }) - it('works on a flow element without siblings where width and height is percentage', async () => { + it('works on a flow element without siblings where width and height is percentage and sets hugging parent to fixed size', async () => { const initialEditor = await renderTestEditorWithCode( makeTestProjectCodeWithSnippet(` @@ -1299,6 +1306,220 @@ describe('Convert to absolute/escape hatch', () => { ) }) }) + + describe('Choosing strategy to not set hugging parent to fixed size', () => { + it('Runs the escape hatch strategy and does not set hugging parent to fixed size', async () => { + const initialEditor = await renderTestEditorWithCode( + makeTestProjectCodeWithSnippet(` + + + + `), + 'await-first-dom-report', + ) + + const targetElement = EP.elementPath([ + [BakedInStoryboardUID, 'scene-aaa', 'app-entity'], + ['aaa', 'bbb'], + ]) + + await selectComponentsForTest(initialEditor, [targetElement]) + + const action = runEscapeHatch([targetElement], 'dont-set-hugging-parent-to-fixed') + + await initialEditor.dispatch([action], true) + await initialEditor.getDispatchFollowUpActionsFinished() + + expect(getPrintedUiJsCode(initialEditor.getEditorState())).toEqual( + makeTestProjectCodeWithSnippet( + ` + + `, + ), + ) + }) + + it('works on a flow element without siblings', async () => { + const initialEditor = await renderTestEditorWithCode( + makeTestProjectCodeWithSnippet(` + + + + `), + 'await-first-dom-report', + ) + + const targetElement = EP.elementPath([ + [BakedInStoryboardUID, 'scene-aaa', 'app-entity'], + ['aaa', 'bbb'], + ]) + + await selectComponentsForTest(initialEditor, [targetElement]) + + const viewBounds = initialEditor.renderedDOM.getByTestId('bbb').getBoundingClientRect() + + const canvas = initialEditor.renderedDOM.getByTestId(CanvasControlsContainerID) + + const viewCenter = canvasPoint({ + x: viewBounds.left + viewBounds.width / 2, + y: viewBounds.top + viewBounds.height / 2, + }) + + await mouseDownAtPoint(canvas, viewCenter) + await mouseMoveToPoint(canvas, offsetPoint(viewCenter, canvasPoint({ x: 15, y: 15 }))) + + await initialEditor.dispatch( + [CanvasActions.setUsersPreferredStrategy(ConvertToAbsoluteAndMoveStrategyID)], + true, + ) + + await mouseUpAtPoint(canvas, offsetPoint(viewCenter, canvasPoint({ x: 15, y: 15 }))) + + expect(getPrintedUiJsCode(initialEditor.getEditorState())).toEqual( + makeTestProjectCodeWithSnippet( + ` + + `, + ), + ) + }) + + it('works on a flow element with all pins', async () => { + const initialEditor = await renderTestEditorWithCode( + makeTestProjectCodeWithSnippet(` + + + + `), + 'await-first-dom-report', + ) + + const targetElement = EP.elementPath([ + [BakedInStoryboardUID, 'scene-aaa', 'app-entity'], + ['aaa', 'bbb'], + ]) + + await selectComponentsForTest(initialEditor, [targetElement]) + + const viewBounds = initialEditor.renderedDOM.getByTestId('bbb').getBoundingClientRect() + + const canvas = initialEditor.renderedDOM.getByTestId(CanvasControlsContainerID) + + const viewCenter = canvasPoint({ + x: viewBounds.left + viewBounds.width / 2, + y: viewBounds.top + viewBounds.height / 2, + }) + + await mouseDownAtPoint(canvas, viewCenter) + await mouseMoveToPoint(canvas, offsetPoint(viewCenter, canvasPoint({ x: 15, y: 15 }))) + + await initialEditor.dispatch( + [CanvasActions.setUsersPreferredStrategy(ConvertToAbsoluteAndMoveStrategyID)], + true, + ) + + await mouseUpAtPoint(canvas, offsetPoint(viewCenter, canvasPoint({ x: 15, y: 15 }))) + + expect(getPrintedUiJsCode(initialEditor.getEditorState())).toEqual( + makeTestProjectCodeWithSnippet( + ` + + `, + ), + ) + }) + + it('works on a flow element without siblings where width and height is percentage', async () => { + const initialEditor = await renderTestEditorWithCode( + makeTestProjectCodeWithSnippet(` + + + + `), + 'await-first-dom-report', + ) + + const targetElement = EP.elementPath([ + [BakedInStoryboardUID, 'scene-aaa', 'app-entity'], + ['aaa', 'bbb'], + ]) + + await selectComponentsForTest(initialEditor, [targetElement]) + + const viewBounds = initialEditor.renderedDOM.getByTestId('bbb').getBoundingClientRect() + + const canvas = initialEditor.renderedDOM.getByTestId(CanvasControlsContainerID) + + const viewCenter = canvasPoint({ + x: viewBounds.left + viewBounds.width / 2, + y: viewBounds.top + viewBounds.height / 2, + }) + + await mouseDownAtPoint(canvas, viewCenter) + await mouseMoveToPoint(canvas, offsetPoint(viewCenter, canvasPoint({ x: 15, y: 15 }))) + + await initialEditor.dispatch( + [CanvasActions.setUsersPreferredStrategy(ConvertToAbsoluteAndMoveStrategyID)], + true, + ) + + await mouseUpAtPoint(canvas, offsetPoint(viewCenter, canvasPoint({ x: 15, y: 15 }))) + + expect(getPrintedUiJsCode(initialEditor.getEditorState())).toEqual( + makeTestProjectCodeWithSnippet( + ` + + `, + ), + ) + }) + }) }) function codeForDragToEscapeHatchProject(flowOrFlex: 'flow' | 'flex'): string { diff --git a/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.tsx index cd85ea830f5f..001ded576c81 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.tsx @@ -80,39 +80,41 @@ import { cssPixelLength } from '../../../inspector/common/css-utils' import type { ProjectContentTreeRoot } from '../../../assets' import { showToastCommand } from '../../commands/show-toast-command' -type SetParentToFixed = 'set-parent-to-fixed' | 'dont-set-parent-to-fixed' +export type SetHuggingParentToFixed = + | 'set-hugging-parent-to-fixed' + | 'dont-set-hugging-parent-to-fixed' -export const ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID = +export const ConvertToAbsoluteAndMoveAndSetHuggingParentFixedStrategyID = 'CONVERT_TO_ABSOLUTE_AND_MOVE_AND_SET_PARENT_FIXED_STRATEGY' export const ConvertToAbsoluteAndMoveStrategyID = 'CONVERT_TO_ABSOLUTE_AND_MOVE_STRATEGY' export const convertToAbsoluteAndMoveStrategy = convertToAbsoluteAndMoveStrategyFactory( - 'dont-set-parent-to-fixed', + 'dont-set-hugging-parent-to-fixed', ) export const convertToAbsoluteAndMoveAndSetParentFixedStrategy = - convertToAbsoluteAndMoveStrategyFactory('set-parent-to-fixed') + convertToAbsoluteAndMoveStrategyFactory('set-hugging-parent-to-fixed') -function getBasicStrategyProperties(setParentToFixed: SetParentToFixed) { - switch (setParentToFixed) { - case 'set-parent-to-fixed': +function getBasicStrategyProperties(setHuggingParentToFixed: SetHuggingParentToFixed) { + switch (setHuggingParentToFixed) { + case 'set-hugging-parent-to-fixed': return { - id: ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID, - name: 'Move (Abs + Set Parent Fixed)', - descriptiveLabel: 'Converting To Absolute And Moving (setting parent to fixed)', + id: ConvertToAbsoluteAndMoveAndSetHuggingParentFixedStrategyID, + name: 'Move (Abs + Set Hugging Parent Fixed)', + descriptiveLabel: 'Converting To Absolute And Moving (setting hugging parent to fixed)', } - case 'dont-set-parent-to-fixed': + case 'dont-set-hugging-parent-to-fixed': return { id: ConvertToAbsoluteAndMoveStrategyID, name: 'Move (Abs)', descriptiveLabel: 'Converting To Absolute And Moving', } default: - assertNever(setParentToFixed) + assertNever(setHuggingParentToFixed) } } -function convertToAbsoluteAndMoveStrategyFactory(setParentToFixed: SetParentToFixed) { +function convertToAbsoluteAndMoveStrategyFactory(setHuggingParentToFixed: SetHuggingParentToFixed) { return ( canvasState: InteractionCanvasState, interactionSession: InteractionSession | null, @@ -166,7 +168,7 @@ function convertToAbsoluteAndMoveStrategyFactory(setParentToFixed: SetParentToFi ) return { - ...getBasicStrategyProperties(setParentToFixed), + ...getBasicStrategyProperties(setHuggingParentToFixed), icon: { category: 'modalities', type: 'moveabs-large', @@ -186,7 +188,8 @@ function convertToAbsoluteAndMoveStrategyFactory(setParentToFixed: SetParentToFi }), ...autoLayoutSiblingsControl, ], // Uses existing hooks in select-mode-hooks.tsx - fitness: setParentToFixed === 'set-parent-to-fixed' ? baseFitness : baseFitness - 0.1, + fitness: + setHuggingParentToFixed === 'set-hugging-parent-to-fixed' ? baseFitness : baseFitness - 0.1, // by default we set the parent to fixed size apply: () => { if ( interactionSession != null && @@ -204,7 +207,7 @@ function convertToAbsoluteAndMoveStrategyFactory(setParentToFixed: SetParentToFi canvasState.startingMetadata, canvasState, snappedDragVector, - setParentToFixed, + setHuggingParentToFixed, ) } const absoluteMoveApplyResult = applyMoveCommon( @@ -322,7 +325,7 @@ export function getEscapeHatchCommands( metadata: ElementInstanceMetadataMap, canvasState: InteractionCanvasState, dragDelta: CanvasVector | null, - setParentToFixed: SetParentToFixed, + setHuggingParentToFixed: SetHuggingParentToFixed, ): { commands: Array intendedBounds: Array @@ -349,7 +352,7 @@ export function getEscapeHatchCommands( ) const setParentsToFixedSizeCommands = - setParentToFixed === 'set-parent-to-fixed' + setHuggingParentToFixed === 'set-hugging-parent-to-fixed' ? createSetParentsToFixedSizeCommands( elementsToConvertToAbsolute, metadata, @@ -706,7 +709,7 @@ function createSetParentsToFixedSizeCommands( return [] } - const setWidthCommands = isCollapsingParent(parentElement, 'width') + const setWidthCommands = isHuggingParent(parentElement, 'width') ? [ setCssLengthProperty( 'always', @@ -717,7 +720,7 @@ function createSetParentsToFixedSizeCommands( ), ] : [] - const setHeightCommands = isCollapsingParent(parentElement, 'width') + const setHeightCommands = isHuggingParent(parentElement, 'width') ? [ setCssLengthProperty( 'always', @@ -743,13 +746,13 @@ function createSetParentsToFixedSizeCommands( return [] } -const CollapsingWidthHeightValues = ['max-content', 'min-content', 'fit-content', 'auto'] +const HuggingWidthHeightValues = ['max-content', 'min-content', 'fit-content', 'auto'] -function isCollapsingParent(element: JSXElement, property: 'width' | 'height') { +function isHuggingParent(element: JSXElement, property: 'width' | 'height') { const simpleAttribute = defaultEither( null, getSimpleAttributeAtPath(right(element.props), PP.create('style', property)), ) - return simpleAttribute == null || CollapsingWidthHeightValues.includes(simpleAttribute) + return simpleAttribute == null || HuggingWidthHeightValues.includes(simpleAttribute) } diff --git a/editor/src/components/canvas/canvas-strategies/strategies/flex-reparent-to-absolute-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/flex-reparent-to-absolute-strategy.tsx index 7ab5d8446dc1..a48e2880e935 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/flex-reparent-to-absolute-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/flex-reparent-to-absolute-strategy.tsx @@ -97,7 +97,7 @@ export function baseFlexReparentToAbsoluteStrategy( canvasState.startingMetadata, canvasState, canvasPoint({ x: 0, y: 0 }), - 'dont-set-parent-to-fixed', + 'dont-set-hugging-parent-to-fixed', ).commands return strategyApplicationResult( diff --git a/editor/src/components/context-menu-items.ts b/editor/src/components/context-menu-items.ts index 3a24a407d194..60ce3cf64ab0 100644 --- a/editor/src/components/context-menu-items.ts +++ b/editor/src/components/context-menu-items.ts @@ -472,7 +472,10 @@ export const escapeHatch: ContextMenuItem = { }, action: (data, dispatch?: EditorDispatch) => { if (data.selectedViews.length > 0) { - requireDispatch(dispatch)([EditorActions.runEscapeHatch(data.selectedViews)], 'everyone') + requireDispatch(dispatch)( + [EditorActions.runEscapeHatch(data.selectedViews, 'set-hugging-parent-to-fixed')], + 'everyone', + ) } }, } diff --git a/editor/src/components/editor/action-types.ts b/editor/src/components/editor/action-types.ts index fe6e776bbe65..69860a1fc0bc 100644 --- a/editor/src/components/editor/action-types.ts +++ b/editor/src/components/editor/action-types.ts @@ -76,6 +76,7 @@ import { ElementPathTrees } from '../../core/shared/element-path-tree' import type { PostActionChoice } from '../canvas/canvas-strategies/post-action-options/post-action-options' import type { FromVSCodeAction } from './actions/actions-from-vscode' import type { ProjectServerState } from './store/project-server-state' +import type { SetHuggingParentToFixed } from '../canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy' export { isLoggedIn, loggedInUser, notLoggedIn } from '../../common/user' export type { LoginState, UserDetails } from '../../common/user' @@ -997,6 +998,7 @@ export interface ForceParseFile { export interface RunEscapeHatch { action: 'RUN_ESCAPE_HATCH' targets: Array + setHuggingParentToFixed: SetHuggingParentToFixed } export interface SetElementsToRerender { diff --git a/editor/src/components/editor/actions/action-creators.ts b/editor/src/components/editor/actions/action-creators.ts index 333866ba9ccc..8f9d8fbdd663 100644 --- a/editor/src/components/editor/actions/action-creators.ts +++ b/editor/src/components/editor/actions/action-creators.ts @@ -249,6 +249,7 @@ import type { TextProp } from '../../text-editor/text-editor' import { ElementPathTrees } from '../../../core/shared/element-path-tree' import type { PostActionChoice } from '../../canvas/canvas-strategies/post-action-options/post-action-options' import type { ProjectServerState } from '../store/project-server-state' +import type { SetHuggingParentToFixed } from '../../canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy' export function clearSelection(): EditorAction { return { @@ -1550,10 +1551,14 @@ export function forceParseFile(filePath: string): ForceParseFile { } } -export function runEscapeHatch(targets: Array): RunEscapeHatch { +export function runEscapeHatch( + targets: Array, + setHuggingParentToFixed: SetHuggingParentToFixed, +): RunEscapeHatch { return { action: 'RUN_ESCAPE_HATCH', targets: targets, + setHuggingParentToFixed: setHuggingParentToFixed, } } diff --git a/editor/src/components/editor/actions/actions.tsx b/editor/src/components/editor/actions/actions.tsx index 617ca68c4ea1..672237c99fd3 100644 --- a/editor/src/components/editor/actions/actions.tsx +++ b/editor/src/components/editor/actions/actions.tsx @@ -5057,7 +5057,7 @@ export const UPDATE_FNS = { editor.jsxMetadata, canvasState, null, - 'set-parent-to-fixed', + action.setHuggingParentToFixed, ).commands return foldAndApplyCommandsSimple(editor, commands) } else { diff --git a/editor/src/components/inspector/sections/layout-section/self-layout-subsection/self-layout-subsection.tsx b/editor/src/components/inspector/sections/layout-section/self-layout-subsection/self-layout-subsection.tsx index dd489db4021c..ad96890fe533 100644 --- a/editor/src/components/inspector/sections/layout-section/self-layout-subsection/self-layout-subsection.tsx +++ b/editor/src/components/inspector/sections/layout-section/self-layout-subsection/self-layout-subsection.tsx @@ -27,7 +27,7 @@ import { useInitialSizeSectionState, } from '../flex-element-subsection/flex-element-subsection' import { GiganticSizePinsSubsection } from './gigantic-size-pins-subsection' -import { runEscapeHatch, selectComponents } from '../../../../editor/actions/action-creators' +import { selectComponents } from '../../../../editor/actions/action-creators' import { UIGridRow } from '../../../widgets/ui-grid-row' import { unless, when } from '../../../../../utils/react-conditionals' import { From a3d88b88099d71306eb559a655342a4a530f64d1 Mon Sep 17 00:00:00 2001 From: Balint Gabor <127662+gbalint@users.noreply.github.com> Date: Wed, 11 Oct 2023 15:29:49 +0200 Subject: [PATCH 04/10] Fix tests --- ...solute-and-move-strategy.spec.browser2.tsx | 29 ++++++++++--------- .../convert-to-absolute-and-move-strategy.tsx | 4 +-- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.spec.browser2.tsx index 14360536975f..d8d879e5680c 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.spec.browser2.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.spec.browser2.tsx @@ -53,7 +53,10 @@ import { getOpeningFragmentLikeTag, } from './fragment-like-helpers.test-utils' import { selectComponentsForTest } from '../../../../utils/utils.test-utils' -import { ConvertToAbsoluteAndMoveStrategyID } from './convert-to-absolute-and-move-strategy' +import { + ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID, + ConvertToAbsoluteAndMoveStrategyID, +} from './convert-to-absolute-and-move-strategy' import CanvasActions from '../../canvas-actions' const complexProject = () => { @@ -675,7 +678,7 @@ describe('Convert to absolute/escape hatch', () => { keyDown('Space') const currentStrategy = renderResult.getEditorState().strategyState.currentStrategy - expect(currentStrategy).toEqual(ConvertToAbsoluteAndMoveStrategyID) + expect(currentStrategy).toEqual(ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID) }) it('becomes the active strategy while space is pressed rather than ancestor metastrategy', async () => { @@ -739,7 +742,7 @@ describe('Convert to absolute/escape hatch', () => { keyDown('Space') const currentStrategy = renderResult.getEditorState().strategyState.currentStrategy - expect(currentStrategy).toEqual(ConvertToAbsoluteAndMoveStrategyID) + expect(currentStrategy).toEqual(ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID) }) ;(['flex', 'flow'] as const).forEach((parentLayoutSystem) => it(`DOES NOT BECOME the active strategy when dragging for multiselection across the hierarchy for ${parentLayoutSystem} parent`, async () => { @@ -784,7 +787,7 @@ describe('Convert to absolute/escape hatch', () => { const midDragStrategy = renderResult.getEditorState().strategyState.currentStrategy expect(midDragStrategy).not.toBeNull() - expect(midDragStrategy).not.toEqual(ConvertToAbsoluteAndMoveStrategyID) + expect(midDragStrategy).not.toEqual(ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID) // Now drag until we have passed the sibling bounds await mouseMoveToPoint(canvasControlsLayer, { @@ -794,12 +797,12 @@ describe('Convert to absolute/escape hatch', () => { const endDragStrategy = renderResult.getEditorState().strategyState.currentStrategy expect(endDragStrategy).not.toBeNull() - expect(endDragStrategy).not.toEqual(ConvertToAbsoluteAndMoveStrategyID) + expect(endDragStrategy).not.toEqual(ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID) // HOWEVER!!!! pressing space now makes the strategy active!!!!!! keyDown('Space') const keydownStrategy = renderResult.getEditorState().strategyState.currentStrategy - expect(keydownStrategy).toEqual(ConvertToAbsoluteAndMoveStrategyID) + expect(keydownStrategy).toEqual(ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID) }), ) ;(['flex', 'flow'] as const).forEach((parentLayoutSystem) => @@ -844,7 +847,7 @@ describe('Convert to absolute/escape hatch', () => { const midDragStrategy = renderResult.getEditorState().strategyState.currentStrategy expect(midDragStrategy).not.toBeNull() - expect(midDragStrategy).not.toEqual(ConvertToAbsoluteAndMoveStrategyID) + expect(midDragStrategy).not.toEqual(ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID) // Now drag until we have passed the sibling bounds await mouseMoveToPoint(canvasControlsLayer, { @@ -854,12 +857,12 @@ describe('Convert to absolute/escape hatch', () => { const endDragStrategy = renderResult.getEditorState().strategyState.currentStrategy expect(endDragStrategy).not.toBeNull() - expect(endDragStrategy).toEqual(ConvertToAbsoluteAndMoveStrategyID) + expect(endDragStrategy).toEqual(ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID) // pressing space keeps the strategy active keyDown('Space') const keydownStrategy = renderResult.getEditorState().strategyState.currentStrategy - expect(keydownStrategy).toEqual(ConvertToAbsoluteAndMoveStrategyID) + expect(keydownStrategy).toEqual(ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID) }), ) @@ -907,7 +910,7 @@ describe('Convert to absolute/escape hatch', () => { const midDragStrategy = renderResult.getEditorState().strategyState.currentStrategy expect(midDragStrategy).not.toBeNull() - expect(midDragStrategy).not.toEqual(ConvertToAbsoluteAndMoveStrategyID) + expect(midDragStrategy).not.toEqual(ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID) // Now drag until we have passed the sibling bounds await mouseMoveToPoint(canvasControlsLayer, { @@ -917,7 +920,7 @@ describe('Convert to absolute/escape hatch', () => { const endDragStrategy = renderResult.getEditorState().strategyState.currentStrategy expect(endDragStrategy).not.toBeNull() - expect(endDragStrategy).toEqual(ConvertToAbsoluteAndMoveStrategyID) + expect(endDragStrategy).toEqual(ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID) }) }, ) @@ -969,7 +972,7 @@ describe('Convert to absolute/escape hatch', () => { const midDragStrategy = renderResult.getEditorState().strategyState.currentStrategy expect(midDragStrategy).not.toBeNull() - expect(midDragStrategy).not.toEqual(ConvertToAbsoluteAndMoveStrategyID) + expect(midDragStrategy).not.toEqual(ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID) // Now drag until we have passed the sibling bounds await mouseMoveToPoint(canvasControlsLayer, { @@ -979,7 +982,7 @@ describe('Convert to absolute/escape hatch', () => { const endDragStrategy = renderResult.getEditorState().strategyState.currentStrategy expect(endDragStrategy).not.toBeNull() - expect(endDragStrategy).toEqual(ConvertToAbsoluteAndMoveStrategyID) + expect(endDragStrategy).toEqual(ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID) await mouseUpAtPoint(canvasControlsLayer, { x: elementBounds.x + 110, diff --git a/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.tsx index 001ded576c81..67557d84767a 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.tsx @@ -84,7 +84,7 @@ export type SetHuggingParentToFixed = | 'set-hugging-parent-to-fixed' | 'dont-set-hugging-parent-to-fixed' -export const ConvertToAbsoluteAndMoveAndSetHuggingParentFixedStrategyID = +export const ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID = 'CONVERT_TO_ABSOLUTE_AND_MOVE_AND_SET_PARENT_FIXED_STRATEGY' export const ConvertToAbsoluteAndMoveStrategyID = 'CONVERT_TO_ABSOLUTE_AND_MOVE_STRATEGY' @@ -99,7 +99,7 @@ function getBasicStrategyProperties(setHuggingParentToFixed: SetHuggingParentToF switch (setHuggingParentToFixed) { case 'set-hugging-parent-to-fixed': return { - id: ConvertToAbsoluteAndMoveAndSetHuggingParentFixedStrategyID, + id: ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID, name: 'Move (Abs + Set Hugging Parent Fixed)', descriptiveLabel: 'Converting To Absolute And Moving (setting hugging parent to fixed)', } From 5c7a14f54a4cd80c2605b11b62a2d767b6eef1a7 Mon Sep 17 00:00:00 2001 From: Balint Gabor <127662+gbalint@users.noreply.github.com> Date: Wed, 11 Oct 2023 15:56:14 +0200 Subject: [PATCH 05/10] Use sizeToVisualDimensions instead of reinventing the wheel --- .../convert-to-absolute-and-move-strategy.tsx | 38 +++---------------- 1 file changed, 6 insertions(+), 32 deletions(-) diff --git a/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.tsx index 67557d84767a..c8c0f31eba8c 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.tsx @@ -79,6 +79,7 @@ import type { ElementPathTrees } from '../../../../core/shared/element-path-tree import { cssPixelLength } from '../../../inspector/common/css-utils' import type { ProjectContentTreeRoot } from '../../../assets' import { showToastCommand } from '../../commands/show-toast-command' +import { sizeToVisualDimensions } from '../../../inspector/inspector-common' export type SetHuggingParentToFixed = | 'set-hugging-parent-to-fixed' @@ -356,6 +357,7 @@ export function getEscapeHatchCommands( ? createSetParentsToFixedSizeCommands( elementsToConvertToAbsolute, metadata, + canvasState.startingElementPathTree, canvasState.projectContents, ) : [] @@ -673,6 +675,7 @@ function getFrameWithoutMargin( function createSetParentsToFixedSizeCommands( elementsToConvertToAbsolute: Array, metadata: ElementInstanceMetadataMap, + pathTree: ElementPathTrees, projectContents: ProjectContentTreeRoot, ): Array { if (elementsToConvertToAbsolute.length > 0) { @@ -698,46 +701,17 @@ function createSetParentsToFixedSizeCommands( return [] } return firstAncestorsHonoringPropsSize.flatMap((ancestor) => { - const parentMetadata = MetadataUtils.findElementByElementPath(metadata, ancestor) - const parentLocalFrame = parentMetadata?.localFrame - if (parentLocalFrame == null || !isFiniteRectangle(parentLocalFrame)) { - return [] - } - const parentElement = MetadataUtils.getJSXElementFromMetadata(metadata, ancestor) if (parentElement == null) { return [] } - const setWidthCommands = isHuggingParent(parentElement, 'width') - ? [ - setCssLengthProperty( - 'always', - ancestor, - PP.create('style', 'width'), - setExplicitCssValue(cssPixelLength(parentLocalFrame.width)), - null, - ), - ] - : [] - const setHeightCommands = isHuggingParent(parentElement, 'width') - ? [ - setCssLengthProperty( - 'always', - ancestor, - PP.create('style', 'height'), - setExplicitCssValue(cssPixelLength(parentLocalFrame.height)), - null, - ), - ] - : [] - - const commands = [...setWidthCommands, ...setHeightCommands] - if (commands.length === 0) { + if (!isHuggingParent(parentElement, 'width') && !isHuggingParent(parentElement, 'height')) { return [] } + return [ - ...commands, + ...sizeToVisualDimensions(metadata, pathTree, ancestor), showToastCommand('Parent is set to fixed size', 'NOTICE', 'set-parent-to-fixed-size'), ] }) From 5fd19331364803785db265be30a365e7baff2416 Mon Sep 17 00:00:00 2001 From: Balint Gabor <127662+gbalint@users.noreply.github.com> Date: Wed, 11 Oct 2023 17:23:49 +0200 Subject: [PATCH 06/10] Fix tests --- ...convert-to-absolute-and-move-strategy.spec.browser2.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.spec.browser2.tsx index 3aa1e3ea00be..ed6612991e3e 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.spec.browser2.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.spec.browser2.tsx @@ -1454,7 +1454,7 @@ describe('Convert to absolute/escape hatch', () => { makeTestProjectCodeWithSnippet( ` @@ -1515,7 +1515,7 @@ describe('Convert to absolute/escape hatch', () => { makeTestProjectCodeWithSnippet( ` @@ -1630,8 +1630,9 @@ describe('Escape hatch strategy on awkward project', () => { style={{ display: 'flex', flexDirection: 'row', - width: '100%', + width: 400, justifyContent: 'space-between', + height: 100, }} data-uid='container' > From 981b90ea57d354bbaed1e14065e6b970ebb09e2e Mon Sep 17 00:00:00 2001 From: Balint Gabor <127662+gbalint@users.noreply.github.com> Date: Thu, 12 Oct 2023 09:26:02 +0200 Subject: [PATCH 07/10] Do not allow hugging strategy when parent is not hugging --- .../convert-to-absolute-and-move-strategy.tsx | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.tsx index 2a02bf4d2c86..b907fceb9f03 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.tsx @@ -101,8 +101,8 @@ function getBasicStrategyProperties(setHuggingParentToFixed: SetHuggingParentToF case 'set-hugging-parent-to-fixed': return { id: ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID, - name: 'Move (Abs + Set Hugging Parent Fixed)', - descriptiveLabel: 'Converting To Absolute And Moving (setting hugging parent to fixed)', + name: 'Move (Abs + Set Parent Fixed)', + descriptiveLabel: 'Converting To Absolute And Moving (setting parent to fixed)', } case 'dont-set-hugging-parent-to-fixed': return { @@ -138,6 +138,19 @@ function convertToAbsoluteAndMoveStrategyFactory(setHuggingParentToFixed: SetHug return null } + if (setHuggingParentToFixed === 'set-hugging-parent-to-fixed') { + const setParentToFixedSizeCommands = createSetParentsToFixedSizeCommands( + retargetedTargets, + canvasState.startingMetadata, + canvasState.startingElementPathTree, + canvasState.projectContents, + ) + + if (setParentToFixedSizeCommands.length === 0) { + return null + } + } + const autoLayoutSiblings = getAutoLayoutSiblings( canvasState.startingMetadata, canvasState.startingElementPathTree, @@ -190,7 +203,7 @@ function convertToAbsoluteAndMoveStrategyFactory(setHuggingParentToFixed: SetHug ...autoLayoutSiblingsControl, ], // Uses existing hooks in select-mode-hooks.tsx fitness: - setHuggingParentToFixed === 'set-hugging-parent-to-fixed' ? baseFitness : baseFitness - 0.1, // by default we set the parent to fixed size + setHuggingParentToFixed === 'set-hugging-parent-to-fixed' ? baseFitness + 0.1 : baseFitness, // by default we set the parent to fixed size apply: () => { if ( interactionSession != null && From ef07dc98807a4990748603fa03f69ce0bba7391c Mon Sep 17 00:00:00 2001 From: Balint Gabor <127662+gbalint@users.noreply.github.com> Date: Thu, 12 Oct 2023 09:30:37 +0200 Subject: [PATCH 08/10] Add comment --- .../strategies/convert-to-absolute-and-move-strategy.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.tsx b/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.tsx index b907fceb9f03..03d6d78122f7 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.tsx @@ -138,6 +138,7 @@ function convertToAbsoluteAndMoveStrategyFactory(setHuggingParentToFixed: SetHug return null } + // When the parent is not hugging, don't offer the strategy which sets it to fixed size if (setHuggingParentToFixed === 'set-hugging-parent-to-fixed') { const setParentToFixedSizeCommands = createSetParentsToFixedSizeCommands( retargetedTargets, From b67f9998b1028210a825259cf468ea152ebf1412 Mon Sep 17 00:00:00 2001 From: Balint Gabor <127662+gbalint@users.noreply.github.com> Date: Thu, 12 Oct 2023 09:39:19 +0200 Subject: [PATCH 09/10] Fix test --- .../strategies/ancestor-metastrategy.spec.browser2.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/src/components/canvas/canvas-strategies/strategies/ancestor-metastrategy.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/ancestor-metastrategy.spec.browser2.tsx index ebe507bcde3c..806af270ede7 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/ancestor-metastrategy.spec.browser2.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/ancestor-metastrategy.spec.browser2.tsx @@ -579,7 +579,7 @@ describe('finds an applicable strategy for the nearest ancestor', () => { runTest(codeElementWithSibling, pathForShallowNestedElement, (editor) => { const strategies = editor.getEditorState().strategyState.sortedApplicableStrategies - expect(strategies?.length).toEqual(3) + expect(strategies?.length).toEqual(2) if (strategies == null) { // here for type assertion throw new Error('`strategies` should not be null') From 3f0dedb3a7e467a13a7c12e4d86787d3f4acf1da Mon Sep 17 00:00:00 2001 From: Balint Gabor <127662+gbalint@users.noreply.github.com> Date: Thu, 12 Oct 2023 11:11:52 +0200 Subject: [PATCH 10/10] Fix tests --- ...solute-and-move-strategy.spec.browser2.tsx | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.spec.browser2.tsx index ed6612991e3e..2b495fcbc37a 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.spec.browser2.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/convert-to-absolute-and-move-strategy.spec.browser2.tsx @@ -54,10 +54,7 @@ import { getOpeningFragmentLikeTag, } from './fragment-like-helpers.test-utils' import { selectComponentsForTest } from '../../../../utils/utils.test-utils' -import { - ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID, - ConvertToAbsoluteAndMoveStrategyID, -} from './convert-to-absolute-and-move-strategy' +import { ConvertToAbsoluteAndMoveStrategyID } from './convert-to-absolute-and-move-strategy' import CanvasActions from '../../canvas-actions' const complexProject = () => { @@ -679,7 +676,7 @@ describe('Convert to absolute/escape hatch', () => { keyDown('Space') const currentStrategy = renderResult.getEditorState().strategyState.currentStrategy - expect(currentStrategy).toEqual(ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID) + expect(currentStrategy).toEqual(ConvertToAbsoluteAndMoveStrategyID) }) it('becomes the active strategy while space is pressed rather than ancestor metastrategy', async () => { @@ -743,7 +740,7 @@ describe('Convert to absolute/escape hatch', () => { keyDown('Space') const currentStrategy = renderResult.getEditorState().strategyState.currentStrategy - expect(currentStrategy).toEqual(ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID) + expect(currentStrategy).toEqual(ConvertToAbsoluteAndMoveStrategyID) }) ;(['flex', 'flow'] as const).forEach((parentLayoutSystem) => it(`DOES NOT BECOME the active strategy when dragging for multiselection across the hierarchy for ${parentLayoutSystem} parent`, async () => { @@ -788,7 +785,7 @@ describe('Convert to absolute/escape hatch', () => { const midDragStrategy = renderResult.getEditorState().strategyState.currentStrategy expect(midDragStrategy).not.toBeNull() - expect(midDragStrategy).not.toEqual(ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID) + expect(midDragStrategy).not.toEqual(ConvertToAbsoluteAndMoveStrategyID) // Now drag until we have passed the sibling bounds await mouseMoveToPoint(canvasControlsLayer, { @@ -798,12 +795,12 @@ describe('Convert to absolute/escape hatch', () => { const endDragStrategy = renderResult.getEditorState().strategyState.currentStrategy expect(endDragStrategy).not.toBeNull() - expect(endDragStrategy).not.toEqual(ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID) + expect(endDragStrategy).not.toEqual(ConvertToAbsoluteAndMoveStrategyID) // HOWEVER!!!! pressing space now makes the strategy active!!!!!! keyDown('Space') const keydownStrategy = renderResult.getEditorState().strategyState.currentStrategy - expect(keydownStrategy).toEqual(ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID) + expect(keydownStrategy).toEqual(ConvertToAbsoluteAndMoveStrategyID) }), ) ;(['flex', 'flow'] as const).forEach((parentLayoutSystem) => @@ -848,7 +845,7 @@ describe('Convert to absolute/escape hatch', () => { const midDragStrategy = renderResult.getEditorState().strategyState.currentStrategy expect(midDragStrategy).not.toBeNull() - expect(midDragStrategy).not.toEqual(ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID) + expect(midDragStrategy).not.toEqual(ConvertToAbsoluteAndMoveStrategyID) // Now drag until we have passed the sibling bounds await mouseMoveToPoint(canvasControlsLayer, { @@ -858,12 +855,12 @@ describe('Convert to absolute/escape hatch', () => { const endDragStrategy = renderResult.getEditorState().strategyState.currentStrategy expect(endDragStrategy).not.toBeNull() - expect(endDragStrategy).toEqual(ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID) + expect(endDragStrategy).toEqual(ConvertToAbsoluteAndMoveStrategyID) // pressing space keeps the strategy active keyDown('Space') const keydownStrategy = renderResult.getEditorState().strategyState.currentStrategy - expect(keydownStrategy).toEqual(ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID) + expect(keydownStrategy).toEqual(ConvertToAbsoluteAndMoveStrategyID) }), ) @@ -911,7 +908,7 @@ describe('Convert to absolute/escape hatch', () => { const midDragStrategy = renderResult.getEditorState().strategyState.currentStrategy expect(midDragStrategy).not.toBeNull() - expect(midDragStrategy).not.toEqual(ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID) + expect(midDragStrategy).not.toEqual(ConvertToAbsoluteAndMoveStrategyID) // Now drag until we have passed the sibling bounds await mouseMoveToPoint(canvasControlsLayer, { @@ -921,7 +918,7 @@ describe('Convert to absolute/escape hatch', () => { const endDragStrategy = renderResult.getEditorState().strategyState.currentStrategy expect(endDragStrategy).not.toBeNull() - expect(endDragStrategy).toEqual(ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID) + expect(endDragStrategy).toEqual(ConvertToAbsoluteAndMoveStrategyID) }) }, ) @@ -973,7 +970,7 @@ describe('Convert to absolute/escape hatch', () => { const midDragStrategy = renderResult.getEditorState().strategyState.currentStrategy expect(midDragStrategy).not.toBeNull() - expect(midDragStrategy).not.toEqual(ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID) + expect(midDragStrategy).not.toEqual(ConvertToAbsoluteAndMoveStrategyID) // Now drag until we have passed the sibling bounds await mouseMoveToPoint(canvasControlsLayer, { @@ -983,7 +980,7 @@ describe('Convert to absolute/escape hatch', () => { const endDragStrategy = renderResult.getEditorState().strategyState.currentStrategy expect(endDragStrategy).not.toBeNull() - expect(endDragStrategy).toEqual(ConvertToAbsoluteAndMoveAndSetParentFixedStrategyID) + expect(endDragStrategy).toEqual(ConvertToAbsoluteAndMoveStrategyID) await mouseUpAtPoint(canvasControlsLayer, { x: elementBounds.x + 110,