From e234b474f7d5075a1d7699eb71213561be87eee8 Mon Sep 17 00:00:00 2001 From: Berci Kormendy Date: Wed, 3 Jul 2024 13:13:18 +0200 Subject: [PATCH 01/15] wip - grid resize --- .../canvas-strategies/canvas-strategies.tsx | 6 +- .../canvas-strategies/interaction-state.ts | 23 +++ .../grid-resize-element-strategy.ts | 83 +++++++++ .../canvas/controls/grid-controls.tsx | 169 ++++++++++++++++++ .../store/store-deep-equality-instances.ts | 17 ++ 5 files changed, 296 insertions(+), 2 deletions(-) create mode 100644 editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts diff --git a/editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx b/editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx index 0301f4ee9dde..41255890b9b4 100644 --- a/editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx +++ b/editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx @@ -66,6 +66,7 @@ import { MetadataUtils } from '../../../core/model/element-metadata-utils' import { gridRearrangeMoveStrategy } from './strategies/grid-rearrange-move-strategy' import { resizeGridStrategy } from './strategies/resize-grid-strategy' import { rearrangeGridSwapStrategy } from './strategies/rearrange-grid-swap-strategy' +import { gridResizeElementStrategy } from './strategies/grid-resize-element-strategy' export type CanvasStrategyFactory = ( canvasState: InteractionCanvasState, @@ -93,8 +94,8 @@ const moveOrReorderStrategies: MetaCanvasStrategy = ( convertToAbsoluteAndMoveStrategy, convertToAbsoluteAndMoveAndSetParentFixedStrategy, reorderSliderStategy, - gridRearrangeMoveStrategy, - rearrangeGridSwapStrategy, + // gridRearrangeMoveStrategy, + // rearrangeGridSwapStrategy, ], ) } @@ -113,6 +114,7 @@ const resizeStrategies: MetaCanvasStrategy = ( flexResizeStrategy, basicResizeStrategy, resizeGridStrategy, + gridResizeElementStrategy, ], ) } diff --git a/editor/src/components/canvas/canvas-strategies/interaction-state.ts b/editor/src/components/canvas/canvas-strategies/interaction-state.ts index e51cb5477744..90b5813318c7 100644 --- a/editor/src/components/canvas/canvas-strategies/interaction-state.ts +++ b/editor/src/components/canvas/canvas-strategies/interaction-state.ts @@ -628,6 +628,28 @@ export function gridCellHandle(params: { id: string }): GridCellHandle { } } +export const RowStart = 'row-start' as const +export const RowEnd = 'row-end' as const +export const ColumnStart = 'column-start' as const +export const ColumnEnd = 'column-end' as const + +export const GridResizeEdges = [RowStart, RowEnd, ColumnStart, ColumnEnd] as const +export type GridResizeEdge = (typeof GridResizeEdges)[number] + +export interface GridResizeHandle { + type: 'GRID_RESIZE_HANDLE' + id: string + edge: GridResizeEdge +} + +export function gridResizeHandle(id: string, edge: GridResizeEdge): GridResizeHandle { + return { + type: 'GRID_RESIZE_HANDLE', + id: id, + edge: edge, + } +} + export type CanvasControlType = | BoundingArea | ResizeHandle @@ -638,6 +660,7 @@ export type CanvasControlType = | BorderRadiusResizeHandle | GridCellHandle | GridAxisHandle + | GridResizeHandle export function isDragToPan( interaction: InteractionSession | null, diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts new file mode 100644 index 000000000000..1a834af153be --- /dev/null +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts @@ -0,0 +1,83 @@ +import { MetadataUtils } from '../../../../core/model/element-metadata-utils' +import * as EP from '../../../../core/shared/element-path' +import type { ElementPath } from '../../../../core/shared/project-file-types' +import { create } from '../../../../core/shared/property-path' +import type { CanvasCommand } from '../../commands/commands' +import { setProperty } from '../../commands/set-property-command' +import { GridControls, GridResizeControls, TargetGridCell } from '../../controls/grid-controls' +import type { CanvasStrategyFactory } from '../canvas-strategies' +import { onlyFitWhenDraggingThisControl } from '../canvas-strategies' +import type { InteractionCanvasState } from '../canvas-strategy-types' +import { + getTargetPathsFromInteractionTarget, + emptyStrategyApplicationResult, + strategyApplicationResult, +} from '../canvas-strategy-types' +import type { InteractionSession } from '../interaction-state' + +export const gridResizeElementStrategy: CanvasStrategyFactory = ( + canvasState: InteractionCanvasState, + interactionSession: InteractionSession | null, +) => { + const selectedElements = getTargetPathsFromInteractionTarget(canvasState.interactionTarget) + if (selectedElements.length !== 1) { + return null + } + + const selectedElement = selectedElements[0] + const isElementInsideGrid = MetadataUtils.isGridLayoutedContainer( + MetadataUtils.findElementByElementPath( + canvasState.startingMetadata, + EP.parentPath(selectedElement), + ), + ) + if (!isElementInsideGrid) { + return null + } + + return { + id: 'grid-cell-resize-strategy', + name: 'Resize Grid Cell', + descriptiveLabel: 'Resize Grid Cell', + icon: { + category: 'tools', + type: 'pointer', + }, + controlsToRender: [ + { + control: GridResizeControls, + props: { target: selectedElement }, + key: `grid-resize-controls-${EP.toString(selectedElement)}`, + show: 'always-visible', + }, + ], + fitness: onlyFitWhenDraggingThisControl(interactionSession, 'GRID_RESIZE_HANDLE', 1), + apply: () => { + if ( + interactionSession == null || + interactionSession.interactionData.type !== 'DRAG' || + interactionSession.interactionData.drag == null || + interactionSession.activeControl.type !== 'GRID_RESIZE_HANDLE' + ) { + return emptyStrategyApplicationResult + } + + return strategyApplicationResult( + resizeGridCellCommands(selectedElement, { + columnEnd: TargetGridCell.current.column + 1, + rowEnd: TargetGridCell.current.row + 1, + }), + ) + }, + } +} + +function resizeGridCellCommands( + elementPath: ElementPath, + { columnEnd, rowEnd }: { columnEnd: number; rowEnd: number }, +): CanvasCommand[] { + return [ + setProperty('always', elementPath, create('style', 'gridColumnEnd'), columnEnd), + setProperty('always', elementPath, create('style', 'gridRowEnd'), rowEnd), + ] +} diff --git a/editor/src/components/canvas/controls/grid-controls.tsx b/editor/src/components/canvas/controls/grid-controls.tsx index 66205b64bf91..57879118405e 100644 --- a/editor/src/components/canvas/controls/grid-controls.tsx +++ b/editor/src/components/canvas/controls/grid-controls.tsx @@ -18,9 +18,11 @@ import { distance, getRectCenter, isFiniteRectangle, + isInfinityRectangle, offsetPoint, pointDifference, windowPoint, + zeroRectIfNullOrInfinity, } from '../../../core/shared/math-utils' import { fromArrayIndex, @@ -38,10 +40,13 @@ import { Substores, useEditorState, useRefEditorState } from '../../editor/store import { useRollYourOwnFeatures } from '../../navigator/left-pane/roll-your-own-pane' import CanvasActions from '../canvas-actions' import { controlForStrategyMemoized } from '../canvas-strategies/canvas-strategy-types' +import type { GridResizeEdge } from '../canvas-strategies/interaction-state' import { + GridResizeEdges, createInteractionViaMouse, gridAxisHandle, gridCellHandle, + gridResizeHandle, } from '../canvas-strategies/interaction-state' import { windowToCanvasCoordinates } from '../dom-lookup' import { CanvasOffsetWrapper } from './canvas-offset-wrapper' @@ -798,3 +803,167 @@ function useMouseMove(activelyDraggingOrResizingCell: string | null) { return { hoveringCell, hoveringStart, mouseCanvasPosition } } + +interface GridResizeControlProps { + target: ElementPath +} + +export const GridResizeControls = controlForStrategyMemoized( + ({ target }) => { + const element = useEditorState( + Substores.metadata, + (store) => MetadataUtils.findElementByElementPath(store.editor.jsxMetadata, target), + 'GridResizeShadow element', + ) + + const dispatch = useDispatch() + const canvasOffsetRef = useRefEditorState((store) => store.editor.canvas.roundedCanvasOffset) + const scaleRef = useRefEditorState((store) => store.editor.canvas.scale) + + const dragging = useEditorState( + Substores.canvas, + (store) => + store.editor.canvas.interactionSession != null && + store.editor.canvas.interactionSession.activeControl.type === 'GRID_RESIZE_HANDLE', + '', + ) + const [offset, setOffset] = React.useState<{ width: number; height: number } | null>(null) + const onMouseMove = React.useCallback( + (e: MouseEvent) => { + if (!dragging) { + return + } + + setOffset((o) => + o == null ? null : { width: o.width + e.movementX, height: o.height + e.movementY }, + ) + }, + [dragging], + ) + + const onMouseUp = React.useCallback(() => setOffset(null), []) + + React.useEffect(() => { + window.addEventListener('mousemove', onMouseMove) + window.addEventListener('mouseup', onMouseUp) + return () => { + window.removeEventListener('mousemove', onMouseMove) + window.removeEventListener('mouseup', onMouseUp) + } + }, [onMouseMove, onMouseUp]) + + const startResizeInteraction = React.useCallback( + (uid: string, edge: GridResizeEdge) => (event: React.MouseEvent) => { + event.stopPropagation() + const frame = zeroRectIfNullOrInfinity(element?.globalFrame ?? null) + setOffset({ width: frame.width, height: frame.height }) + const start = windowToCanvasCoordinates( + scaleRef.current, + canvasOffsetRef.current, + windowPoint({ x: event.nativeEvent.x, y: event.nativeEvent.y }), + ) + dispatch([ + CanvasActions.createInteractionSession( + createInteractionViaMouse( + start.canvasPositionRounded, + Modifier.modifiersForEvent(event), + gridResizeHandle(uid, edge), + 'zero-drag-not-permitted', + ), + ), + ]) + }, + [canvasOffsetRef, dispatch, element?.globalFrame, scaleRef], + ) + + const colorTheme = useColorTheme() + + if ( + element == null || + element.globalFrame == null || + isInfinityRectangle(element.globalFrame) + ) { + return null + } + + return ( + +
+ {GridResizeEdges.map((edge) => ( +
+
+
+ ))} +
+ + ) + }, +) + +function gridEdgeToGridArea(edge: GridResizeEdge): string { + switch (edge) { + case 'column-end': + return 'ce' + case 'column-start': + return 'cs' + case 'row-end': + return 're' + case 'row-start': + return 'rs' + default: + assertNever(edge) + } +} + +function gridEdgeToWidthHeight(edge: GridResizeEdge): { + width: number + height: number + borderRadius: number +} { + const LONG_EDGE = 20 + const SHORT_EDGE = 6 + switch (edge) { + case 'column-end': + case 'column-start': + return { width: SHORT_EDGE, height: LONG_EDGE, borderRadius: SHORT_EDGE / 2 } + case 'row-end': + case 'row-start': + return { width: LONG_EDGE, height: SHORT_EDGE, borderRadius: SHORT_EDGE / 2 } + default: + assertNever(edge) + } +} diff --git a/editor/src/components/editor/store/store-deep-equality-instances.ts b/editor/src/components/editor/store/store-deep-equality-instances.ts index 8f5cef751b70..7c0227f6db6d 100644 --- a/editor/src/components/editor/store/store-deep-equality-instances.ts +++ b/editor/src/components/editor/store/store-deep-equality-instances.ts @@ -455,6 +455,8 @@ import type { ZeroDragPermitted, GridCellHandle, GridAxisHandle, + GridResizeHandle, + GridResizeEdge, } from '../../canvas/canvas-strategies/interaction-state' import { boundingArea, @@ -465,6 +467,7 @@ import { resizeHandle, gridCellHandle, gridAxisHandle, + gridResizeHandle, } from '../../canvas/canvas-strategies/interaction-state' import type { Modifiers } from '../../../utils/modifiers' import type { @@ -2870,6 +2873,15 @@ export const GridAxisHandleKeepDeepEquality: KeepDeepEqualityCall = + combine2EqualityCalls( + (handle) => handle.id, + createCallWithTripleEquals(), + (handle) => handle.edge, + createCallWithTripleEquals(), + gridResizeHandle, + ) + export const CanvasControlTypeKeepDeepEquality: KeepDeepEqualityCall = ( oldValue, newValue, @@ -2920,6 +2932,11 @@ export const CanvasControlTypeKeepDeepEquality: KeepDeepEqualityCall Date: Wed, 3 Jul 2024 13:25:31 +0200 Subject: [PATCH 02/15] remove targetgridcell global --- .../grid-resize-element-strategy.ts | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts index 1a834af153be..e5eff559bab8 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts @@ -1,10 +1,12 @@ import { MetadataUtils } from '../../../../core/model/element-metadata-utils' import * as EP from '../../../../core/shared/element-path' +import { offsetPoint } from '../../../../core/shared/math-utils' import type { ElementPath } from '../../../../core/shared/project-file-types' import { create } from '../../../../core/shared/property-path' import type { CanvasCommand } from '../../commands/commands' import { setProperty } from '../../commands/set-property-command' -import { GridControls, GridResizeControls, TargetGridCell } from '../../controls/grid-controls' +import { GridResizeControls } from '../../controls/grid-controls' +import { canvasPointToWindowPoint } from '../../dom-lookup' import type { CanvasStrategyFactory } from '../canvas-strategies' import { onlyFitWhenDraggingThisControl } from '../canvas-strategies' import type { InteractionCanvasState } from '../canvas-strategy-types' @@ -14,10 +16,12 @@ import { strategyApplicationResult, } from '../canvas-strategy-types' import type { InteractionSession } from '../interaction-state' +import { getGridCellUnderMouse } from './grid-helpers' export const gridResizeElementStrategy: CanvasStrategyFactory = ( canvasState: InteractionCanvasState, interactionSession: InteractionSession | null, + customState, ) => { const selectedElements = getTargetPathsFromInteractionTarget(canvasState.interactionTarget) if (selectedElements.length !== 1) { @@ -62,11 +66,31 @@ export const gridResizeElementStrategy: CanvasStrategyFactory = ( return emptyStrategyApplicationResult } + const mouseWindowPoint = canvasPointToWindowPoint( + offsetPoint( + interactionSession.interactionData.dragStart, + interactionSession.interactionData.drag, + ), + canvasState.scale, + canvasState.canvasOffset, + ) + + let targetCell = customState.targetGridCell + const cellUnderMouse = getGridCellUnderMouse(mouseWindowPoint) + if (cellUnderMouse != null) { + targetCell = cellUnderMouse.coordinates + } + + if (targetCell == null) { + return emptyStrategyApplicationResult + } + return strategyApplicationResult( resizeGridCellCommands(selectedElement, { - columnEnd: TargetGridCell.current.column + 1, - rowEnd: TargetGridCell.current.row + 1, + columnEnd: targetCell.column, + rowEnd: targetCell.row, }), + { targetGridCell: targetCell }, ) }, } From 30482ef9f4affd83d5025c5d4a1d453901cd5798 Mon Sep 17 00:00:00 2001 From: Berci Kormendy Date: Wed, 3 Jul 2024 13:51:16 +0200 Subject: [PATCH 03/15] x --- .../canvas/canvas-strategies/canvas-strategies.tsx | 4 ++-- .../strategies/grid-resize-element-strategy.ts | 12 +++++++++--- .../src/components/canvas/controls/grid-controls.tsx | 8 +++++--- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx b/editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx index 41255890b9b4..f72b60fc2257 100644 --- a/editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx +++ b/editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx @@ -94,8 +94,8 @@ const moveOrReorderStrategies: MetaCanvasStrategy = ( convertToAbsoluteAndMoveStrategy, convertToAbsoluteAndMoveAndSetParentFixedStrategy, reorderSliderStategy, - // gridRearrangeMoveStrategy, - // rearrangeGridSwapStrategy, + gridRearrangeMoveStrategy, + rearrangeGridSwapStrategy, ], ) } diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts index e5eff559bab8..f78b932bead3 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts @@ -5,7 +5,7 @@ import type { ElementPath } from '../../../../core/shared/project-file-types' import { create } from '../../../../core/shared/property-path' import type { CanvasCommand } from '../../commands/commands' import { setProperty } from '../../commands/set-property-command' -import { GridResizeControls } from '../../controls/grid-controls' +import { GridControls, GridResizeControls } from '../../controls/grid-controls' import { canvasPointToWindowPoint } from '../../dom-lookup' import type { CanvasStrategyFactory } from '../canvas-strategies' import { onlyFitWhenDraggingThisControl } from '../canvas-strategies' @@ -54,6 +54,12 @@ export const gridResizeElementStrategy: CanvasStrategyFactory = ( key: `grid-resize-controls-${EP.toString(selectedElement)}`, show: 'always-visible', }, + { + control: GridControls, + props: {}, + key: `grid-controls-${EP.toString(selectedElement)}`, + show: 'always-visible', + }, ], fitness: onlyFitWhenDraggingThisControl(interactionSession, 'GRID_RESIZE_HANDLE', 1), apply: () => { @@ -87,8 +93,8 @@ export const gridResizeElementStrategy: CanvasStrategyFactory = ( return strategyApplicationResult( resizeGridCellCommands(selectedElement, { - columnEnd: targetCell.column, - rowEnd: targetCell.row, + columnEnd: targetCell.column + 1, + rowEnd: targetCell.row + 1, }), { targetGridCell: targetCell }, ) diff --git a/editor/src/components/canvas/controls/grid-controls.tsx b/editor/src/components/canvas/controls/grid-controls.tsx index 1a28156efd66..3bddd1be1ff8 100644 --- a/editor/src/components/canvas/controls/grid-controls.tsx +++ b/editor/src/components/canvas/controls/grid-controls.tsx @@ -895,6 +895,7 @@ export const GridResizeControls = controlForStrategyMemoized {GridResizeEdges.map((edge) => ( @@ -908,15 +909,16 @@ export const GridResizeControls = controlForStrategyMemoized
@@ -947,8 +949,8 @@ function gridEdgeToWidthHeight(edge: GridResizeEdge): { height: number borderRadius: number } { - const LONG_EDGE = 20 - const SHORT_EDGE = 6 + const LONG_EDGE = 24 + const SHORT_EDGE = 4 switch (edge) { case 'column-end': case 'column-start': From 40cc8f08e6411122d9ba46938b6b2aa9c534a9b3 Mon Sep 17 00:00:00 2001 From: Berci Kormendy Date: Wed, 3 Jul 2024 14:14:19 +0200 Subject: [PATCH 04/15] grid controls all directions --- .../canvas/controls/grid-controls.tsx | 61 +++++++++++++------ 1 file changed, 44 insertions(+), 17 deletions(-) diff --git a/editor/src/components/canvas/controls/grid-controls.tsx b/editor/src/components/canvas/controls/grid-controls.tsx index 3bddd1be1ff8..26d98df5fffa 100644 --- a/editor/src/components/canvas/controls/grid-controls.tsx +++ b/editor/src/components/canvas/controls/grid-controls.tsx @@ -15,6 +15,7 @@ import { import type { CanvasPoint, CanvasRectangle } from '../../../core/shared/math-utils' import { canvasPoint, + canvasRectangle, distance, getRectCenter, isFiniteRectangle, @@ -813,28 +814,54 @@ export const GridResizeControls = controlForStrategyMemoized store.editor.canvas.roundedCanvasOffset) const scaleRef = useRefEditorState((store) => store.editor.canvas.scale) - const dragging = useEditorState( - Substores.canvas, - (store) => - store.editor.canvas.interactionSession != null && - store.editor.canvas.interactionSession.activeControl.type === 'GRID_RESIZE_HANDLE', - '', + const resizeControlRef = useRefEditorState((store) => + store.editor.canvas.interactionSession?.activeControl.type !== 'GRID_RESIZE_HANDLE' + ? null + : store.editor.canvas.interactionSession.activeControl, ) - const [offset, setOffset] = React.useState<{ width: number; height: number } | null>(null) + + const [bounds, setBounds] = React.useState(null) const onMouseMove = React.useCallback( (e: MouseEvent) => { - if (!dragging) { + if (resizeControlRef.current == null) { return } - setOffset((o) => - o == null ? null : { width: o.width + e.movementX, height: o.height + e.movementY }, + let delta: CanvasRectangle = canvasRectangle({ x: 0, y: 0, width: 0, height: 0 }) + switch (resizeControlRef.current.edge) { + case 'column-end': + delta.width = e.movementX + break + case 'column-start': + delta.x = e.movementX + delta.width = -e.movementX + break + case 'row-start': + delta.y = e.movementY + delta.height = -e.movementY + break + case 'row-end': + delta.height = e.movementY + break + default: + assertNever(resizeControlRef.current.edge) + } + + setBounds((o) => + o == null + ? null + : canvasRectangle({ + x: o.x + delta.x, + y: o.y + delta.y, + width: o.width + delta.width, + height: o.height + delta.height, + }), ) }, - [dragging], + [resizeControlRef], ) - const onMouseUp = React.useCallback(() => setOffset(null), []) + const onMouseUp = React.useCallback(() => setBounds(null), []) React.useEffect(() => { window.addEventListener('mousemove', onMouseMove) @@ -849,7 +876,7 @@ export const GridResizeControls = controlForStrategyMemoized (event: React.MouseEvent) => { event.stopPropagation() const frame = zeroRectIfNullOrInfinity(element?.globalFrame ?? null) - setOffset({ width: frame.width, height: frame.height }) + setBounds(frame) const start = windowToCanvasCoordinates( scaleRef.current, canvasOffsetRef.current, @@ -887,10 +914,10 @@ export const GridResizeControls = controlForStrategyMemoized Date: Wed, 3 Jul 2024 15:54:22 +0200 Subject: [PATCH 05/15] grid element resize (sans rollover) --- .../strategies/grid-helpers.ts | 38 +++++++++++ .../grid-resize-element-strategy.ts | 67 +++++++++++++------ .../rearrange-grid-swap-strategy.ts | 42 +----------- 3 files changed, 85 insertions(+), 62 deletions(-) diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.ts index 1d9248f19f94..30d74211b0dc 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.ts @@ -3,6 +3,7 @@ import { MetadataUtils } from '../../../../core/model/element-metadata-utils' import type { ElementInstanceMetadataMap, GridElementProperties, + GridPosition, } from '../../../../core/shared/element-template' import type { CanvasVector } from '../../../../core/shared/math-utils' import { @@ -18,6 +19,8 @@ import type { GridCellCoordinates } from '../../controls/grid-controls' import { gridCellCoordinates } from '../../controls/grid-controls' import { canvasPointToWindowPoint } from '../../dom-lookup' import type { DragInteractionData } from '../interaction-state' +import { stripNulls } from '../../../../core/shared/array-utils' +import { optionalMap } from '../../../../core/shared/optional-utils' export function getGridCellUnderMouse(mousePoint: WindowPoint) { return getGridCellAtPoint(mousePoint, false) @@ -152,3 +155,38 @@ export function runGridRearrangeMove( targetGridCell: newTargetGridCell, } } + +export function gridPositionToValue(p: GridPosition | null | undefined): string | number | null { + if (p == null) { + return null + } + if (p === 'auto') { + return 'auto' + } + + return p.numericalPosition +} + +export function setGridProps( + elementPath: ElementPath, + gridProps: Partial, +): CanvasCommand[] { + return stripNulls([ + optionalMap( + (s) => setProperty('always', elementPath, create('style', 'gridColumnStart'), s), + gridPositionToValue(gridProps?.gridColumnStart), + ), + optionalMap( + (s) => setProperty('always', elementPath, create('style', 'gridColumnEnd'), s), + gridPositionToValue(gridProps?.gridColumnEnd), + ), + optionalMap( + (s) => setProperty('always', elementPath, create('style', 'gridRowStart'), s), + gridPositionToValue(gridProps?.gridRowStart), + ), + optionalMap( + (s) => setProperty('always', elementPath, create('style', 'gridRowEnd'), s), + gridPositionToValue(gridProps?.gridRowEnd), + ), + ]) +} diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts index f78b932bead3..1300ef97cd18 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts @@ -1,10 +1,8 @@ import { MetadataUtils } from '../../../../core/model/element-metadata-utils' import * as EP from '../../../../core/shared/element-path' +import type { GridElementProperties } from '../../../../core/shared/element-template' import { offsetPoint } from '../../../../core/shared/math-utils' -import type { ElementPath } from '../../../../core/shared/project-file-types' -import { create } from '../../../../core/shared/property-path' -import type { CanvasCommand } from '../../commands/commands' -import { setProperty } from '../../commands/set-property-command' +import { assertNever } from '../../../../core/shared/utils' import { GridControls, GridResizeControls } from '../../controls/grid-controls' import { canvasPointToWindowPoint } from '../../dom-lookup' import type { CanvasStrategyFactory } from '../canvas-strategies' @@ -16,7 +14,7 @@ import { strategyApplicationResult, } from '../canvas-strategy-types' import type { InteractionSession } from '../interaction-state' -import { getGridCellUnderMouse } from './grid-helpers' +import { getGridCellUnderMouse, setGridProps } from './grid-helpers' export const gridResizeElementStrategy: CanvasStrategyFactory = ( canvasState: InteractionCanvasState, @@ -91,23 +89,48 @@ export const gridResizeElementStrategy: CanvasStrategyFactory = ( return emptyStrategyApplicationResult } - return strategyApplicationResult( - resizeGridCellCommands(selectedElement, { - columnEnd: targetCell.column + 1, - rowEnd: targetCell.row + 1, - }), - { targetGridCell: targetCell }, - ) + let gridProps: GridElementProperties = MetadataUtils.findElementByElementPath( + canvasState.startingMetadata, + selectedElement, + )?.specialSizeMeasurements.elementGridProperties ?? { + gridColumnEnd: { numericalPosition: 0 }, + gridColumnStart: { numericalPosition: 0 }, + gridRowEnd: { numericalPosition: 0 }, + gridRowStart: { numericalPosition: 0 }, + } + + switch (interactionSession.activeControl.edge) { + case 'column-start': + gridProps = { + ...gridProps, + gridColumnStart: { numericalPosition: targetCell.column }, + } + break + case 'column-end': + gridProps = { + ...gridProps, + gridColumnEnd: { numericalPosition: targetCell.column + 1 }, + } + break + case 'row-end': + gridProps = { + ...gridProps, + gridRowEnd: { numericalPosition: targetCell.row + 1 }, + } + break + case 'row-start': + gridProps = { + ...gridProps, + gridRowStart: { numericalPosition: targetCell.row }, + } + break + default: + assertNever(interactionSession.activeControl.edge) + } + + return strategyApplicationResult(setGridProps(selectedElement, gridProps), { + targetGridCell: targetCell, + }) }, } } - -function resizeGridCellCommands( - elementPath: ElementPath, - { columnEnd, rowEnd }: { columnEnd: number; rowEnd: number }, -): CanvasCommand[] { - return [ - setProperty('always', elementPath, create('style', 'gridColumnEnd'), columnEnd), - setProperty('always', elementPath, create('style', 'gridRowEnd'), rowEnd), - ] -} diff --git a/editor/src/components/canvas/canvas-strategies/strategies/rearrange-grid-swap-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/rearrange-grid-swap-strategy.ts index 680166359653..0517d2691b85 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/rearrange-grid-swap-strategy.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/rearrange-grid-swap-strategy.ts @@ -1,23 +1,16 @@ import { MetadataUtils } from '../../../../core/model/element-metadata-utils' -import { stripNulls } from '../../../../core/shared/array-utils' import * as EP from '../../../../core/shared/element-path' -import type { - ElementInstanceMetadata, - GridElementProperties, - GridPosition, -} from '../../../../core/shared/element-template' +import type { ElementInstanceMetadata } from '../../../../core/shared/element-template' import { isFiniteRectangle, offsetPoint, rectContainsPointInclusive, } from '../../../../core/shared/math-utils' -import { optionalMap } from '../../../../core/shared/optional-utils' import type { ElementPath } from '../../../../core/shared/project-file-types' import { create } from '../../../../core/shared/property-path' import type { CanvasCommand } from '../../commands/commands' import { deleteProperties } from '../../commands/delete-properties-command' import { rearrangeChildren } from '../../commands/rearrange-children-command' -import { setProperty } from '../../commands/set-property-command' import { GridControls } from '../../controls/grid-controls' import { recurseIntoChildrenOfMapOrFragment } from '../../gap-utils' import type { CanvasStrategyFactory } from '../canvas-strategies' @@ -29,6 +22,7 @@ import { strategyApplicationResult, } from '../canvas-strategy-types' import type { InteractionSession } from '../interaction-state' +import { setGridProps } from './grid-helpers' export const rearrangeGridSwapStrategy: CanvasStrategyFactory = ( canvasState: InteractionCanvasState, @@ -137,38 +131,6 @@ const GridPositioningProps: Array = [ 'gridRowEnd', ] -function gridPositionToValue(p: GridPosition | null): string | number | null { - if (p == null) { - return null - } - if (p === 'auto') { - return 'auto' - } - - return p.numericalPosition -} - -function setGridProps(elementPath: ElementPath, gridProps: GridElementProperties): CanvasCommand[] { - return stripNulls([ - optionalMap( - (s) => setProperty('always', elementPath, create('style', 'gridColumnStart'), s), - gridPositionToValue(gridProps.gridColumnStart), - ), - optionalMap( - (s) => setProperty('always', elementPath, create('style', 'gridColumnEnd'), s), - gridPositionToValue(gridProps.gridColumnEnd), - ), - optionalMap( - (s) => setProperty('always', elementPath, create('style', 'gridRowStart'), s), - gridPositionToValue(gridProps.gridRowStart), - ), - optionalMap( - (s) => setProperty('always', elementPath, create('style', 'gridRowEnd'), s), - gridPositionToValue(gridProps.gridRowEnd), - ), - ]) -} - function swapChildrenCommands({ grabbedElementUid, swapToElementUid, From 5767f5ebdae2377682b81724d155ba53ba4f7044 Mon Sep 17 00:00:00 2001 From: Berci Kormendy Date: Tue, 9 Jul 2024 10:57:58 +0200 Subject: [PATCH 06/15] wip --- .../grid-resize-element-strategy.ts | 55 +++++++++++++++++-- .../canvas/controls/grid-controls.tsx | 45 +++++++++++---- 2 files changed, 83 insertions(+), 17 deletions(-) diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts index 1300ef97cd18..aa614244598f 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts @@ -1,6 +1,6 @@ import { MetadataUtils } from '../../../../core/model/element-metadata-utils' import * as EP from '../../../../core/shared/element-path' -import type { GridElementProperties } from '../../../../core/shared/element-template' +import type { GridElementProperties, GridPosition } from '../../../../core/shared/element-template' import { offsetPoint } from '../../../../core/shared/math-utils' import { assertNever } from '../../../../core/shared/utils' import { GridControls, GridResizeControls } from '../../controls/grid-controls' @@ -79,7 +79,7 @@ export const gridResizeElementStrategy: CanvasStrategyFactory = ( canvasState.canvasOffset, ) - let targetCell = customState.targetGridCell + let targetCell = customState.grid.targetCell const cellUnderMouse = getGridCellUnderMouse(mouseWindowPoint) if (cellUnderMouse != null) { targetCell = cellUnderMouse.coordinates @@ -128,9 +128,54 @@ export const gridResizeElementStrategy: CanvasStrategyFactory = ( assertNever(interactionSession.activeControl.edge) } - return strategyApplicationResult(setGridProps(selectedElement, gridProps), { - targetGridCell: targetCell, - }) + return strategyApplicationResult( + setGridProps(selectedElement, gridPropsWithDragOver(gridProps)), + { + grid: { ...customState.grid, targetCell }, + }, + ) }, } } + +function orderedGridPositions({ + start, + end, +}: { + start: GridPosition | null + end: GridPosition | null +}): { + start: GridPosition | null + end: GridPosition | null +} { + if ( + start == null || + start === 'auto' || + start.numericalPosition == null || + end == null || + end === 'auto' || + end.numericalPosition == null + ) { + return { start, end } + } + + return start.numericalPosition <= end.numericalPosition + ? { start, end } + : { + start: { numericalPosition: end.numericalPosition - 1 }, + end: { numericalPosition: end.numericalPosition + 1 }, + } +} + +function gridPropsWithDragOver(props: GridElementProperties): GridElementProperties { + const { start: gridColumnStart, end: gridColumnEnd } = orderedGridPositions({ + start: props.gridColumnStart, + end: props.gridColumnEnd, + }) + const { start: gridRowStart, end: gridRowEnd } = orderedGridPositions({ + start: props.gridRowStart, + end: props.gridRowEnd, + }) + + return { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } +} diff --git a/editor/src/components/canvas/controls/grid-controls.tsx b/editor/src/components/canvas/controls/grid-controls.tsx index 6f6e0b69f986..a6ef8050549d 100644 --- a/editor/src/components/canvas/controls/grid-controls.tsx +++ b/editor/src/components/canvas/controls/grid-controls.tsx @@ -789,6 +789,38 @@ interface GridResizeControlProps { target: ElementPath } +function boundsWithDragOver({ + bounds, + delta, +}: { + bounds: CanvasRectangle + delta: CanvasRectangle +}): CanvasRectangle { + const offsetWithDelta = canvasRectangle({ + x: bounds.x + delta.x, + y: bounds.y + delta.y, + width: bounds.width + delta.width, + height: bounds.height + delta.height, + }) + + const { x, width } = + offsetWithDelta.width >= 0 + ? offsetWithDelta + : { x: bounds.x + offsetWithDelta.width, width: -offsetWithDelta.width * 2 } + + const { y, height } = + offsetWithDelta.height >= 0 + ? offsetWithDelta + : { y: bounds.y + offsetWithDelta.height, height: -offsetWithDelta.height * 2 } + + return canvasRectangle({ + x, + y, + width, + height, + }) +} + export const GridResizeControls = controlForStrategyMemoized( ({ target }) => { const element = useEditorState( @@ -834,16 +866,7 @@ export const GridResizeControls = controlForStrategyMemoized - o == null - ? null - : canvasRectangle({ - x: o.x + delta.x, - y: o.y + delta.y, - width: o.width + delta.width, - height: o.height + delta.height, - }), - ) + setBounds((o) => (o == null ? null : boundsWithDragOver({ bounds: o, delta: delta }))) }, [resizeControlRef], ) @@ -883,8 +906,6 @@ export const GridResizeControls = controlForStrategyMemoized Date: Tue, 9 Jul 2024 11:31:18 +0200 Subject: [PATCH 07/15] resize control dragover --- .../canvas/controls/grid-controls.tsx | 113 ++++++++---------- 1 file changed, 52 insertions(+), 61 deletions(-) diff --git a/editor/src/components/canvas/controls/grid-controls.tsx b/editor/src/components/canvas/controls/grid-controls.tsx index a6ef8050549d..3c06b9a9f05b 100644 --- a/editor/src/components/canvas/controls/grid-controls.tsx +++ b/editor/src/components/canvas/controls/grid-controls.tsx @@ -53,6 +53,14 @@ import { windowToCanvasCoordinates } from '../dom-lookup' import { CanvasOffsetWrapper } from './canvas-offset-wrapper' import { useColorTheme } from '../../../uuiui' import { gridCellTargetId } from '../canvas-strategies/strategies/grid-helpers' +import { resizeBoundingBoxFromSide } from '../canvas-strategies/strategies/resize-helpers' +import type { EdgePosition } from '../canvas-types' +import { + EdgePositionBottom, + EdgePositionLeft, + EdgePositionRight, + EdgePositionTop, +} from '../canvas-types' export const GridCellTestId = (elementPath: ElementPath) => `grid-cell-${EP.toString(elementPath)}` @@ -789,38 +797,6 @@ interface GridResizeControlProps { target: ElementPath } -function boundsWithDragOver({ - bounds, - delta, -}: { - bounds: CanvasRectangle - delta: CanvasRectangle -}): CanvasRectangle { - const offsetWithDelta = canvasRectangle({ - x: bounds.x + delta.x, - y: bounds.y + delta.y, - width: bounds.width + delta.width, - height: bounds.height + delta.height, - }) - - const { x, width } = - offsetWithDelta.width >= 0 - ? offsetWithDelta - : { x: bounds.x + offsetWithDelta.width, width: -offsetWithDelta.width * 2 } - - const { y, height } = - offsetWithDelta.height >= 0 - ? offsetWithDelta - : { y: bounds.y + offsetWithDelta.height, height: -offsetWithDelta.height * 2 } - - return canvasRectangle({ - x, - y, - width, - height, - }) -} - export const GridResizeControls = controlForStrategyMemoized( ({ target }) => { const element = useEditorState( @@ -839,39 +815,38 @@ export const GridResizeControls = controlForStrategyMemoized + store.editor.canvas.interactionSession?.interactionData.type !== 'DRAG' + ? null + : store.editor.canvas.interactionSession?.interactionData.drag, + ) + + const [startingBounds, setStartingBounds] = React.useState(null) const [bounds, setBounds] = React.useState(null) - const onMouseMove = React.useCallback( - (e: MouseEvent) => { - if (resizeControlRef.current == null) { - return - } + const onMouseMove = React.useCallback(() => { + if (resizeControlRef.current == null || dragRef.current == null) { + return + } - let delta: CanvasRectangle = canvasRectangle({ x: 0, y: 0, width: 0, height: 0 }) - switch (resizeControlRef.current.edge) { - case 'column-end': - delta.width = e.movementX - break - case 'column-start': - delta.x = e.movementX - delta.width = -e.movementX - break - case 'row-start': - delta.y = e.movementY - delta.height = -e.movementY - break - case 'row-end': - delta.height = e.movementY - break - default: - assertNever(resizeControlRef.current.edge) - } + if (startingBounds == null) { + return + } - setBounds((o) => (o == null ? null : boundsWithDragOver({ bounds: o, delta: delta }))) - }, - [resizeControlRef], - ) + setBounds( + resizeBoundingBoxFromSide( + startingBounds, + dragRef.current, + gridEdgeToEdgePosition(resizeControlRef.current.edge), + 'non-center-based', + null, + ), + ) + }, [dragRef, resizeControlRef, startingBounds]) - const onMouseUp = React.useCallback(() => setBounds(null), []) + const onMouseUp = React.useCallback(() => { + setBounds(null) + setStartingBounds(null) + }, []) React.useEffect(() => { window.addEventListener('mousemove', onMouseMove) @@ -887,6 +862,7 @@ export const GridResizeControls = controlForStrategyMemoized Date: Tue, 9 Jul 2024 11:59:59 +0200 Subject: [PATCH 08/15] wip --- .../strategies/grid-resize-element-strategy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts index aa614244598f..22eb894f850d 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts @@ -163,7 +163,7 @@ function orderedGridPositions({ ? { start, end } : { start: { numericalPosition: end.numericalPosition - 1 }, - end: { numericalPosition: end.numericalPosition + 1 }, + end: { numericalPosition: start.numericalPosition + 1 }, } } From 3ec164c748eb53be759471b9720d265288f715bc Mon Sep 17 00:00:00 2001 From: Berci Kormendy Date: Tue, 9 Jul 2024 12:24:52 +0200 Subject: [PATCH 09/15] wip - grid element resize dragover --- .../strategies/grid-helpers.ts | 23 +++++++++++-------- .../grid-resize-element-strategy.ts | 2 +- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.ts index 10acca6ad7e6..ab087e3204ad 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.ts @@ -13,7 +13,7 @@ import { windowRectangle, type WindowPoint, } from '../../../../core/shared/math-utils' -import { create } from '../../../../core/shared/property-path' +import * as PP from '../../../../core/shared/property-path' import type { CanvasCommand } from '../../commands/commands' import { setProperty } from '../../commands/set-property-command' import { canvasPointToWindowPoint } from '../../dom-lookup' @@ -24,6 +24,7 @@ import type { GridCustomStrategyState } from '../canvas-strategy-types' import type { GridCellCoordinates } from '../../controls/grid-controls' import { gridCellCoordinates } from '../../controls/grid-controls' import * as EP from '../../../../core/shared/element-path' +import { deleteProperties } from '../../commands/delete-properties-command' export function getGridCellUnderMouse(mousePoint: WindowPoint) { return getGridCellAtPoint(mousePoint, false) @@ -165,10 +166,10 @@ export function runGridRearrangeMove( return { commands: [ - setProperty('always', targetElement, create('style', 'gridColumnStart'), column.start), - setProperty('always', targetElement, create('style', 'gridColumnEnd'), column.end), - setProperty('always', targetElement, create('style', 'gridRowStart'), row.start), - setProperty('always', targetElement, create('style', 'gridRowEnd'), row.end), + setProperty('always', targetElement, PP.create('style', 'gridColumnStart'), column.start), + setProperty('always', targetElement, PP.create('style', 'gridColumnEnd'), column.end), + setProperty('always', targetElement, PP.create('style', 'gridRowStart'), row.start), + setProperty('always', targetElement, PP.create('style', 'gridRowEnd'), row.end), ], targetCell: newTargetCell, originalRootCell: rootCell, @@ -193,20 +194,24 @@ export function setGridProps( gridProps: Partial, ): CanvasCommand[] { return stripNulls([ + deleteProperties('always', elementPath, [ + PP.create('style', 'gridColumn'), + PP.create('style', 'gridRow'), + ]), optionalMap( - (s) => setProperty('always', elementPath, create('style', 'gridColumnStart'), s), + (s) => setProperty('always', elementPath, PP.create('style', 'gridColumnStart'), s), gridPositionToValue(gridProps?.gridColumnStart), ), optionalMap( - (s) => setProperty('always', elementPath, create('style', 'gridColumnEnd'), s), + (s) => setProperty('always', elementPath, PP.create('style', 'gridColumnEnd'), s), gridPositionToValue(gridProps?.gridColumnEnd), ), optionalMap( - (s) => setProperty('always', elementPath, create('style', 'gridRowStart'), s), + (s) => setProperty('always', elementPath, PP.create('style', 'gridRowStart'), s), gridPositionToValue(gridProps?.gridRowStart), ), optionalMap( - (s) => setProperty('always', elementPath, create('style', 'gridRowEnd'), s), + (s) => setProperty('always', elementPath, PP.create('style', 'gridRowEnd'), s), gridPositionToValue(gridProps?.gridRowEnd), ), ]) diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts index 22eb894f850d..18f59c7240d6 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts @@ -159,7 +159,7 @@ function orderedGridPositions({ return { start, end } } - return start.numericalPosition <= end.numericalPosition + return start.numericalPosition < end.numericalPosition ? { start, end } : { start: { numericalPosition: end.numericalPosition - 1 }, From a519624b12024fa68b847015ed3f0c5c6f41ba5f Mon Sep 17 00:00:00 2001 From: Berci Kormendy Date: Tue, 9 Jul 2024 12:48:01 +0200 Subject: [PATCH 10/15] scaling --- .../canvas/controls/grid-controls.tsx | 38 +++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/editor/src/components/canvas/controls/grid-controls.tsx b/editor/src/components/canvas/controls/grid-controls.tsx index 3c06b9a9f05b..aede3f7d1752 100644 --- a/editor/src/components/canvas/controls/grid-controls.tsx +++ b/editor/src/components/canvas/controls/grid-controls.tsx @@ -56,6 +56,7 @@ import { gridCellTargetId } from '../canvas-strategies/strategies/grid-helpers' import { resizeBoundingBoxFromSide } from '../canvas-strategies/strategies/resize-helpers' import type { EdgePosition } from '../canvas-types' import { + CSSCursor, EdgePositionBottom, EdgePositionLeft, EdgePositionRight, @@ -807,7 +808,11 @@ export const GridResizeControls = controlForStrategyMemoized store.editor.canvas.roundedCanvasOffset) - const scaleRef = useRefEditorState((store) => store.editor.canvas.scale) + const scale = useEditorState( + Substores.canvas, + (store) => store.editor.canvas.scale, + 'GridResizingControl scale', + ) const resizeControlRef = useRefEditorState((store) => store.editor.canvas.interactionSession?.activeControl.type !== 'GRID_RESIZE_HANDLE' @@ -864,7 +869,7 @@ export const GridResizeControls = controlForStrategyMemoized
Date: Tue, 9 Jul 2024 15:03:52 +0200 Subject: [PATCH 11/15] tests --- ...-resize-element-strategy.spec.browser2.tsx | 282 ++++++++++++++++++ .../canvas/controls/grid-controls.tsx | 5 +- 2 files changed, 285 insertions(+), 2 deletions(-) create mode 100644 editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.spec.browser2.tsx diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.spec.browser2.tsx new file mode 100644 index 000000000000..eccff9e9e4e7 --- /dev/null +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.spec.browser2.tsx @@ -0,0 +1,282 @@ +import * as EP from '../../../../core/shared/element-path' +import { getRectCenter, localRectangle } from '../../../../core/shared/math-utils' +import { selectComponentsForTest } from '../../../../utils/utils.test-utils' +import { GridResizeEdgeTestId } from '../../controls/grid-controls' +import { mouseDragFromPointToPoint } from '../../event-helpers.test-utils' +import type { EditorRenderResult } from '../../ui-jsx.test-utils' +import { renderTestEditorWithCode } from '../../ui-jsx.test-utils' +import type { GridResizeEdge } from '../interaction-state' +import { gridCellTargetId } from './grid-helpers' + +async function runCellResizeTest( + editor: EditorRenderResult, + edge: GridResizeEdge, + dragToCellTestId: string, +) { + const elementPathToDrag = EP.fromString('sb/scene/grid/ddd') + + await selectComponentsForTest(editor, [elementPathToDrag]) + + const resizeControl = editor.renderedDOM.getByTestId(GridResizeEdgeTestId(edge)) + const targetGridCell = editor.renderedDOM.getByTestId(dragToCellTestId) + + await mouseDragFromPointToPoint( + resizeControl, + { + x: resizeControl.getBoundingClientRect().x + 2, + y: resizeControl.getBoundingClientRect().y + 2, + }, + getRectCenter( + localRectangle({ + x: targetGridCell.getBoundingClientRect().x, + y: targetGridCell.getBoundingClientRect().y, + width: targetGridCell.getBoundingClientRect().width, + height: targetGridCell.getBoundingClientRect().height, + }), + ), + ) +} + +describe('grid rearrange move strategy', () => { + describe('column-end', () => { + it('can enlarge element', async () => { + const editor = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') + + await runCellResizeTest( + editor, + 'column-end', + gridCellTargetId(EP.fromString('sb/scene/grid'), 2, 10), + ) + + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('grid-child').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '11', + gridColumnStart: '7', + gridRowEnd: '3', + gridRowStart: '2', + }) + }) + + it('can shrink element', async () => { + const editor = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') + + await runCellResizeTest( + editor, + 'column-end', + gridCellTargetId(EP.fromString('sb/scene/grid'), 2, 8), + ) + + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('grid-child').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '9', + gridColumnStart: '7', + gridRowEnd: '3', + gridRowStart: '2', + }) + }) + }) + + describe('column-start', () => { + it('can enlarge element', async () => { + const editor = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') + + await runCellResizeTest( + editor, + 'column-start', + gridCellTargetId(EP.fromString('sb/scene/grid'), 2, 6), + ) + + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('grid-child').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '10', + gridColumnStart: '6', + gridRowEnd: '3', + gridRowStart: '2', + }) + }) + + it('can shrink element', async () => { + const editor = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') + + await runCellResizeTest( + editor, + 'column-start', + gridCellTargetId(EP.fromString('sb/scene/grid'), 2, 8), + ) + + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('grid-child').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '10', + gridColumnStart: '8', + gridRowEnd: '3', + gridRowStart: '2', + }) + }) + }) + + describe('row-end', () => { + it('can resize element', async () => { + const editor = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') + + await runCellResizeTest( + editor, + 'row-end', + gridCellTargetId(EP.fromString('sb/scene/grid'), 3, 6), + ) + { + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('grid-child').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '10', + gridColumnStart: '7', + gridRowEnd: '4', + gridRowStart: '2', + }) + } + + await runCellResizeTest( + editor, + 'row-end', + gridCellTargetId(EP.fromString('sb/scene/grid'), 2, 8), + ) + { + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('grid-child').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '10', + gridColumnStart: '7', + gridRowEnd: '3', + gridRowStart: '2', + }) + } + }) + }) + + describe('row-start', () => { + it('can resize element', async () => { + const editor = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') + + await runCellResizeTest( + editor, + 'row-start', + gridCellTargetId(EP.fromString('sb/scene/grid'), 1, 6), + ) + + { + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('grid-child').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '10', + gridColumnStart: '7', + gridRowEnd: '3', + gridRowStart: '1', + }) + } + + { + await runCellResizeTest( + editor, + 'row-start', + gridCellTargetId(EP.fromString('sb/scene/grid'), 2, 8), + ) + + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = + editor.renderedDOM.getByTestId('grid-child').style + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '10', + gridColumnStart: '7', + gridRowEnd: '3', + gridRowStart: '2', + }) + } + }) + }) +}) + +const ProjectCode = `import * as React from 'react' +import { Scene, Storyboard, Placeholder } from 'utopia-api' + +export var storyboard = ( + + +
+
+ + +
+
+ + +) +` diff --git a/editor/src/components/canvas/controls/grid-controls.tsx b/editor/src/components/canvas/controls/grid-controls.tsx index aede3f7d1752..8a4fc2f08e67 100644 --- a/editor/src/components/canvas/controls/grid-controls.tsx +++ b/editor/src/components/canvas/controls/grid-controls.tsx @@ -15,7 +15,6 @@ import { import type { CanvasPoint, CanvasRectangle } from '../../../core/shared/math-utils' import { canvasPoint, - canvasRectangle, distance, getRectCenter, isFiniteRectangle, @@ -794,6 +793,8 @@ function useMouseMove(activelyDraggingOrResizingCell: string | null) { return { hoveringStart, mouseCanvasPosition } } +export const GridResizeEdgeTestId = (edge: GridResizeEdge) => `grid-resize-edge-${edge}` + interface GridResizeControlProps { target: ElementPath } @@ -930,7 +931,7 @@ export const GridResizeControls = controlForStrategyMemoized
Date: Tue, 9 Jul 2024 15:54:32 +0200 Subject: [PATCH 12/15] remove resize edge constants --- .../canvas/canvas-strategies/interaction-state.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/editor/src/components/canvas/canvas-strategies/interaction-state.ts b/editor/src/components/canvas/canvas-strategies/interaction-state.ts index 90b5813318c7..52e118ef560e 100644 --- a/editor/src/components/canvas/canvas-strategies/interaction-state.ts +++ b/editor/src/components/canvas/canvas-strategies/interaction-state.ts @@ -628,12 +628,7 @@ export function gridCellHandle(params: { id: string }): GridCellHandle { } } -export const RowStart = 'row-start' as const -export const RowEnd = 'row-end' as const -export const ColumnStart = 'column-start' as const -export const ColumnEnd = 'column-end' as const - -export const GridResizeEdges = [RowStart, RowEnd, ColumnStart, ColumnEnd] as const +export const GridResizeEdges = ['row-start', 'row-end', 'column-start', 'column-end'] as const export type GridResizeEdge = (typeof GridResizeEdges)[number] export interface GridResizeHandle { From c471b7ac68bee6609434bb3c058fa86c13fec73d Mon Sep 17 00:00:00 2001 From: Berci Kormendy Date: Tue, 9 Jul 2024 18:06:14 +0200 Subject: [PATCH 13/15] add grid cell resize strategy to isResizableStrategy --- .../components/canvas/canvas-strategies/canvas-strategies.tsx | 2 +- .../strategies/grid-resize-element-strategy.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx b/editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx index 09eb3df56842..b21d9353b610 100644 --- a/editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx +++ b/editor/src/components/canvas/canvas-strategies/canvas-strategies.tsx @@ -523,7 +523,7 @@ export function isResizableStrategy(canvasStrategy: CanvasStrategy): boolean { case 'FLEX_RESIZE_BASIC': case 'FLEX_RESIZE': case 'BASIC_RESIZE': - // TODO add grid cell resize + case 'GRID-CELL-RESIZE-STRATEGY': return true default: return false diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts index 18f59c7240d6..c20d488ad159 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts @@ -38,7 +38,7 @@ export const gridResizeElementStrategy: CanvasStrategyFactory = ( } return { - id: 'grid-cell-resize-strategy', + id: 'GRID-CELL-RESIZE-STRATEGY', name: 'Resize Grid Cell', descriptiveLabel: 'Resize Grid Cell', icon: { From 6cd52afe7558d9ffa25a388d6f61357f54638d6e Mon Sep 17 00:00:00 2001 From: Berci Kormendy Date: Tue, 9 Jul 2024 18:07:32 +0200 Subject: [PATCH 14/15] use isGridCell --- .../strategies/grid-resize-element-strategy.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts index c20d488ad159..04240ca5dc31 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts @@ -27,11 +27,9 @@ export const gridResizeElementStrategy: CanvasStrategyFactory = ( } const selectedElement = selectedElements[0] - const isElementInsideGrid = MetadataUtils.isGridLayoutedContainer( - MetadataUtils.findElementByElementPath( - canvasState.startingMetadata, - EP.parentPath(selectedElement), - ), + const isElementInsideGrid = MetadataUtils.isGridCell( + canvasState.startingMetadata, + selectedElement, ) if (!isElementInsideGrid) { return null From c22a06a5254ddb695189e3eb48342f73148ef37e Mon Sep 17 00:00:00 2001 From: Berci Kormendy Date: Tue, 9 Jul 2024 18:08:12 +0200 Subject: [PATCH 15/15] naming: setGridPropsCommands --- .../canvas/canvas-strategies/strategies/grid-helpers.ts | 2 +- .../strategies/grid-resize-element-strategy.ts | 4 ++-- .../strategies/rearrange-grid-swap-strategy.ts | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.ts index ab087e3204ad..4cb5d3b333f2 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-helpers.ts @@ -189,7 +189,7 @@ export function gridPositionToValue(p: GridPosition | null | undefined): string return p.numericalPosition } -export function setGridProps( +export function setGridPropsCommands( elementPath: ElementPath, gridProps: Partial, ): CanvasCommand[] { diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts index 04240ca5dc31..ed7f3139c487 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-resize-element-strategy.ts @@ -14,7 +14,7 @@ import { strategyApplicationResult, } from '../canvas-strategy-types' import type { InteractionSession } from '../interaction-state' -import { getGridCellUnderMouse, setGridProps } from './grid-helpers' +import { getGridCellUnderMouse, setGridPropsCommands } from './grid-helpers' export const gridResizeElementStrategy: CanvasStrategyFactory = ( canvasState: InteractionCanvasState, @@ -127,7 +127,7 @@ export const gridResizeElementStrategy: CanvasStrategyFactory = ( } return strategyApplicationResult( - setGridProps(selectedElement, gridPropsWithDragOver(gridProps)), + setGridPropsCommands(selectedElement, gridPropsWithDragOver(gridProps)), { grid: { ...customState.grid, targetCell }, }, diff --git a/editor/src/components/canvas/canvas-strategies/strategies/rearrange-grid-swap-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/rearrange-grid-swap-strategy.ts index 09214813c5bb..b6f7d54c5bb7 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/rearrange-grid-swap-strategy.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/rearrange-grid-swap-strategy.ts @@ -22,7 +22,7 @@ import { strategyApplicationResult, } from '../canvas-strategy-types' import type { InteractionSession } from '../interaction-state' -import { setGridProps } from './grid-helpers' +import { setGridPropsCommands } from './grid-helpers' export const rearrangeGridSwapStrategy: CanvasStrategyFactory = ( canvasState: InteractionCanvasState, @@ -168,11 +168,11 @@ function swapChildrenCommands({ grabbedElement.elementPath, GridPositioningProps.map((p) => create('style', p)), ), - ...setGridProps( + ...setGridPropsCommands( grabbedElement.elementPath, swapToElement.specialSizeMeasurements.elementGridProperties, ), - ...setGridProps( + ...setGridPropsCommands( swapToElement.elementPath, grabbedElement.specialSizeMeasurements.elementGridProperties, ),