Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplified layout multi-select support. #4376

Merged
merged 2 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
import type { ElementInstanceMetadataMap } from '../../../../core/shared/element-template'
import { isJSXElement } from '../../../../core/shared/element-template'
import { MetadataUtils } from '../../../../core/model/element-metadata-utils'
import type { CanvasRectangle, CanvasVector } from '../../../../core/shared/math-utils'
import type { CanvasVector } from '../../../../core/shared/math-utils'
import {
canvasRectangle,
canvasVector,
nullIfInfinity,
offsetPoint,
scaleVector,
zeroRectangle,
} from '../../../../core/shared/math-utils'
import type { KeyCharacter } from '../../../../utils/keyboard'
import Keyboard from '../../../../utils/keyboard'
import type { AllElementProps } from '../../../editor/store/editor-state'
import { withUnderlyingTarget } from '../../../editor/store/editor-state'
import type { CanvasFrameAndTarget, EdgePosition } from '../../canvas-types'
import { EdgePositionBottom, EdgePositionRight } from '../../canvas-types'
import type { CanvasCommand } from '../../commands/commands'
Expand All @@ -30,27 +24,24 @@ import {
} from '../canvas-strategy-types'
import type { InteractionSession } from '../interaction-state'
import { resizeBoundingBox, supportsAbsoluteResize } from './resize-helpers'
import {
childrenBoundsToSnapTo,
createResizeCommands,
} from './shared-absolute-resize-strategy-helpers'
import { childrenBoundsToSnapTo } from './shared-absolute-resize-strategy-helpers'
import { changeBounds } from './shared-absolute-resize-strategy-helpers'
import type { AccumulatedPresses } from './shared-keyboard-strategy-helpers'
import {
accumulatePresses,
addOrMergeIntendedBounds,
getKeyboardStrategyGuidelines,
getLastKeyPressState,
getMovementDeltaFromKey,
} from './shared-keyboard-strategy-helpers'
import { getMultiselectBounds } from './shared-move-strategies-helpers'
import { retargetStrategyToChildrenOfFragmentLikeElements } from './fragment-like-helpers'
import type { ElementPath } from '../../../../core/shared/project-file-types'
import type { ProjectContentTreeRoot } from '../../../../components/assets'
import type { InspectorStrategy } from '../../../../components/inspector/inspector-strategies/inspector-strategy'
import type { ElementPathTrees } from '../../../../core/shared/element-path-tree'
import { gatherParentAndSiblingTargets } from '../../controls/guideline-helpers'
import { uniqBy } from '../../../../core/shared/array-utils'
import * as EP from '../../../../core/shared/element-path'
import type { ElementInstanceMetadataMap } from '../../../../core/shared/element-template'
import type { AllElementProps } from '../../../editor/store/editor-state'

interface VectorAndEdge {
movement: CanvasVector
Expand Down Expand Up @@ -124,97 +115,6 @@ function getFitness(interactionSession: InteractionSession | null): number {

export const ResizeMinimumValue = 1

export interface ChangeBoundsResult {
commands: Array<CanvasCommand>
intendedBounds: Array<CanvasFrameAndTarget>
}

export function changeBounds(
projectContents: ProjectContentTreeRoot,
startingMetadata: ElementInstanceMetadataMap,
selectedElements: Array<ElementPath>,
originalFrame: CanvasRectangle,
originalIntendedBounds: Array<CanvasFrameAndTarget>,
edgePosition: EdgePosition,
movement: CanvasVector,
): ChangeBoundsResult {
let commands: Array<CanvasCommand> = []
let intendedBounds: Array<CanvasFrameAndTarget> = originalIntendedBounds

selectedElements.forEach((selectedElement) => {
const element = withUnderlyingTarget(selectedElement, projectContents, null, (_, e) => e)

const elementMetadata = MetadataUtils.findElementByElementPath(
startingMetadata,
selectedElement,
)
const elementParentBounds =
elementMetadata?.specialSizeMeasurements.immediateParentBounds ?? null
const elementParentFlexDirection =
elementMetadata?.specialSizeMeasurements.parentFlexDirection ?? null
const elementGlobalFrame = nullIfInfinity(elementMetadata?.globalFrame ?? null)

if (element != null && isJSXElement(element)) {
const elementResult = createResizeCommands(
element,
selectedElement,
edgePosition,
movement,
elementGlobalFrame,
elementParentBounds,
elementParentFlexDirection,
)
commands.push(...elementResult.commands)
if (elementResult.intendedBounds != null) {
intendedBounds = addOrMergeIntendedBounds(
intendedBounds,
originalFrame,
elementResult.intendedBounds,
)
}
}
})

return {
commands: commands,
intendedBounds: intendedBounds,
}
}

export function resizeInspectorStrategy(
projectContents: ProjectContentTreeRoot,
originalFrame: CanvasRectangle,
edgePosition: EdgePosition,
movement: CanvasVector,
): InspectorStrategy {
return {
name: 'Resize by pixels',
strategy: (
metadata: ElementInstanceMetadataMap,
selectedElements: Array<ElementPath>,
_elementPathTree: ElementPathTrees,
_allElementProps: AllElementProps,
): Array<CanvasCommand> | null => {
let commands: Array<CanvasCommand> = []
const changeBoundsResult = changeBounds(
projectContents,
metadata,
selectedElements,
originalFrame,
[],
edgePosition,
movement,
)
commands.push(...changeBoundsResult.commands)
commands.push(
pushIntendedBoundsAndUpdateGroups(changeBoundsResult.intendedBounds, 'starting-metadata'),
)
commands.push(setElementsToRerenderCommand(selectedElements))
return commands
},
}
}

export function keyboardAbsoluteResizeStrategy(
canvasState: InteractionCanvasState,
interactionSession: InteractionSession | null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,36 @@ import type { LayoutEdgeProp, LayoutPinnedProp } from '../../../../core/layout/l
import { framePointForPinnedProp } from '../../../../core/layout/layout-helpers-new'
import { mapDropNulls, stripNulls } from '../../../../core/shared/array-utils'
import { isLeft, isRight, right } from '../../../../core/shared/either'
import type {
ElementInstanceMetadataMap,
JSXElement,
import {
isJSXElement,
type ElementInstanceMetadataMap,
type JSXElement,
} from '../../../../core/shared/element-template'
import type { CanvasPoint, CanvasRectangle, CanvasVector } from '../../../../core/shared/math-utils'
import { roundRectangleToNearestWhole } from '../../../../core/shared/math-utils'
import type {
CanvasPoint,
CanvasRectangle,
CanvasVector,
LocalRectangle,
} from '../../../../core/shared/math-utils'
import {
canvasPoint,
canvasVector,
isInfinityRectangle,
nullIfInfinity,
pointDifference,
roundRectangleToNearestWhole,
zeroCanvasRect,
zeroLocalRect,
} from '../../../../core/shared/math-utils'
import type { ElementPath } from '../../../../core/shared/project-file-types'
import type { AllElementProps } from '../../../editor/store/editor-state'
import { stylePropPathMappingFn } from '../../../inspector/common/property-path-hooks'
import { type CanvasFrameAndTarget, type EdgePosition } from '../../canvas-types'
import {
EdgePositionRight,
type CanvasFrameAndTarget,
type EdgePosition,
EdgePositionBottom,
} from '../../canvas-types'
import { pickPointOnRect, snapPoint } from '../../canvas-utils'
import type { AdjustCssLengthProperties } from '../../commands/adjust-css-length-command'
import {
Expand All @@ -31,7 +47,14 @@ import { resizeBoundingBox } from './resize-helpers'
import type { FlexDirection } from '../../../inspector/common/css-utils'
import type { ElementPathTrees } from '../../../../core/shared/element-path-tree'
import { replaceNonDOMElementPathsWithTheirChildrenRecursive } from './fragment-like-helpers'
import type { CanvasCommand } from '../../commands/commands'
import type { ProjectContentTreeRoot } from '../../../../components/assets'
import { MetadataUtils } from '../../../../core/model/element-metadata-utils'
import { addOrMergeIntendedBounds } from './shared-keyboard-strategy-helpers'
import type { InspectorStrategy } from '../../../../components/inspector/inspector-strategies/inspector-strategy'
import { pushIntendedBoundsAndUpdateGroups } from '../../commands/push-intended-bounds-and-update-groups-command'
import { setElementsToRerenderCommand } from '../../commands/set-elements-to-rerender-command'
import { withUnderlyingTarget } from '../../../../components/editor/store/editor-state'

export function createResizeCommands(
element: JSXElement,
Expand Down Expand Up @@ -274,3 +297,141 @@ function pinsFromEdgePosition(edgePosition: EdgePosition): Array<LayoutEdgeProp>
bottomEdge ? 'bottom' : null,
])
}

export interface ChangeBoundsResult {
commands: Array<CanvasCommand>
intendedBounds: Array<CanvasFrameAndTarget>
}

export function changeBounds(
projectContents: ProjectContentTreeRoot,
startingMetadata: ElementInstanceMetadataMap,
selectedElements: Array<ElementPath>,
originalFrame: CanvasRectangle,
originalIntendedBounds: Array<CanvasFrameAndTarget>,
edgePosition: EdgePosition,
movement: CanvasVector,
): ChangeBoundsResult {
let commands: Array<CanvasCommand> = []
let intendedBounds: Array<CanvasFrameAndTarget> = originalIntendedBounds

selectedElements.forEach((selectedElement) => {
const element = withUnderlyingTarget(selectedElement, projectContents, null, (_, e) => e)

const elementMetadata = MetadataUtils.findElementByElementPath(
startingMetadata,
selectedElement,
)
const elementParentBounds =
elementMetadata?.specialSizeMeasurements.immediateParentBounds ?? null
const elementParentFlexDirection =
elementMetadata?.specialSizeMeasurements.parentFlexDirection ?? null
const elementGlobalFrame = nullIfInfinity(elementMetadata?.globalFrame ?? null)

if (element != null && isJSXElement(element)) {
const elementResult = createResizeCommands(
element,
selectedElement,
edgePosition,
movement,
elementGlobalFrame,
elementParentBounds,
elementParentFlexDirection,
)
commands.push(...elementResult.commands)
if (elementResult.intendedBounds != null) {
intendedBounds = addOrMergeIntendedBounds(
intendedBounds,
originalFrame,
elementResult.intendedBounds,
)
}
}
})

return {
commands: commands,
intendedBounds: intendedBounds,
}
}

export function resizeInspectorStrategy(
projectContents: ProjectContentTreeRoot,
originalFrame: CanvasRectangle,
edgePosition: EdgePosition,
movement: CanvasVector,
): InspectorStrategy {
return {
name: 'Resize by pixels',
strategy: (
metadata: ElementInstanceMetadataMap,
selectedElements: Array<ElementPath>,
_elementPathTree: ElementPathTrees,
_allElementProps: AllElementProps,
): Array<CanvasCommand> | null => {
let commands: Array<CanvasCommand> = []
const changeBoundsResult = changeBounds(
projectContents,
metadata,
selectedElements,
originalFrame,
[],
edgePosition,
movement,
)
commands.push(...changeBoundsResult.commands)
commands.push(
pushIntendedBoundsAndUpdateGroups(changeBoundsResult.intendedBounds, 'starting-metadata'),
)
commands.push(setElementsToRerenderCommand(selectedElements))
return commands
},
}
}

export function directResizeInspectorStrategy(
projectContents: ProjectContentTreeRoot,
widthOrHeight: 'width' | 'height',
newPixelValue: number,
): InspectorStrategy {
return {
name: 'Resize to pixel size',
strategy: (
metadata: ElementInstanceMetadataMap,
selectedElements: Array<ElementPath>,
_elementPathTree: ElementPathTrees,
_allElementProps: AllElementProps,
): Array<CanvasCommand> | null => {
let commands: Array<CanvasCommand> = []
for (const selectedElement of selectedElements) {
const edgePosition: EdgePosition =
widthOrHeight === 'width' ? EdgePositionRight : EdgePositionBottom
const elementMetadata = MetadataUtils.findElementByElementPath(metadata, selectedElement)
const originalFrame = elementMetadata?.globalFrame
Copy link
Contributor

@balazsbajorics balazsbajorics Oct 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be the localFrame instead? are we using localFrame anywhere anymore? – edit: for size currently the localFrame and globalFrame matches, currently... that is until we start adding scaling or rotation support!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went back and forth a little on this, IIRC the pre-existing code pushed me more in the direction of the global canvas frame.

const defaultedOriginalFrame: CanvasRectangle =
originalFrame == null || isInfinityRectangle(originalFrame)
? zeroCanvasRect
: originalFrame
const movement: CanvasVector = canvasVector({
x: widthOrHeight === 'width' ? newPixelValue - defaultedOriginalFrame.width : 0,
y: widthOrHeight === 'height' ? newPixelValue - defaultedOriginalFrame.height : 0,
})
const changeBoundsResult = changeBounds(
projectContents,
metadata,
[selectedElement],
defaultedOriginalFrame,
[],
edgePosition,
movement,
)
commands.push(...changeBoundsResult.commands)
commands.push(
pushIntendedBoundsAndUpdateGroups(changeBoundsResult.intendedBounds, 'starting-metadata'),
)
}
commands.push(setElementsToRerenderCommand(selectedElements))
return commands
},
}
}
Loading
Loading