fix(editor): restore auto-layout instance dragging
This commit is contained in:
parent
c38778094d
commit
08b9e56a15
|
|
@ -11,6 +11,26 @@ import { applyMoveSnap } from '#vue/shared/input/move-snap'
|
|||
import type { DragMove } from '#vue/shared/input/types'
|
||||
|
||||
const AUTO_LAYOUT_REORDER_CLICK_SLOP = 3
|
||||
const AUTO_LAYOUT_CROSS_AXIS_DRAG_TOLERANCE = 96
|
||||
|
||||
function isInsideAutoLayoutDragBounds(parentId: string, cx: number, cy: number, editor: Editor) {
|
||||
const parent = editor.graph.getNode(parentId)
|
||||
if (!parent) return false
|
||||
const abs = editor.graph.getAbsolutePosition(parentId)
|
||||
const isRow = parent.layoutMode === 'HORIZONTAL'
|
||||
const mainStart = isRow ? abs.x : abs.y
|
||||
const mainSize = isRow ? parent.width : parent.height
|
||||
const crossStart = isRow ? abs.y : abs.x
|
||||
const crossSize = isRow ? parent.height : parent.width
|
||||
const main = isRow ? cx : cy
|
||||
const cross = isRow ? cy : cx
|
||||
return (
|
||||
main >= mainStart - AUTO_LAYOUT_BREAK_THRESHOLD &&
|
||||
main <= mainStart + mainSize + AUTO_LAYOUT_BREAK_THRESHOLD &&
|
||||
cross >= crossStart - AUTO_LAYOUT_CROSS_AXIS_DRAG_TOLERANCE &&
|
||||
cross <= crossStart + crossSize + AUTO_LAYOUT_CROSS_AXIS_DRAG_TOLERANCE
|
||||
)
|
||||
}
|
||||
|
||||
export function detectAutoLayoutParent(editor: Editor): string | undefined {
|
||||
if (editor.state.selectedIds.size !== 1) return undefined
|
||||
|
|
@ -32,8 +52,7 @@ export function handleMoveMove(d: DragMove, cx: number, cy: number, editor: Edit
|
|||
let dy = cy - d.startY
|
||||
|
||||
if (d.autoLayoutParentId && !d.brokeFromAutoLayout) {
|
||||
const dist = Math.sqrt(dx * dx + dy * dy)
|
||||
if (dist < AUTO_LAYOUT_BREAK_THRESHOLD) {
|
||||
if (isInsideAutoLayoutDragBounds(d.autoLayoutParentId, cx, cy, editor)) {
|
||||
computeAutoLayoutIndicator(d, cx, cy, editor)
|
||||
return
|
||||
}
|
||||
|
|
@ -84,7 +103,7 @@ export function handleMoveUp(d: DragMove, editor: Editor) {
|
|||
editor.setDropTarget(null)
|
||||
return
|
||||
}
|
||||
for (const id of editor.state.selectedIds) {
|
||||
for (const id of d.originals.keys()) {
|
||||
editor.reorderInAutoLayout(id, indicator.parentId, indicator.index)
|
||||
}
|
||||
editor.setDropTarget(null)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import type { Editor } from '@open-pencil/core/editor'
|
||||
|
||||
import { duplicateAndDrag } from '#vue/shared/input/duplicate-drag'
|
||||
import { detectAutoLayoutParent } from '#vue/shared/input/move'
|
||||
import type { DragState } from '#vue/shared/input/types'
|
||||
|
||||
type MoveOriginal = { x: number; y: number; parentId: string }
|
||||
|
|
@ -10,9 +9,30 @@ export function selectionIsLocked(editor: Editor) {
|
|||
return [...editor.state.selectedIds].every((id) => editor.graph.getNode(id)?.locked)
|
||||
}
|
||||
|
||||
function autoLayoutMoveTarget(id: string, editor: Editor): string {
|
||||
let current = editor.graph.getNode(id)
|
||||
let target = current
|
||||
|
||||
while (current?.parentId) {
|
||||
const parent = editor.graph.getNode(current.parentId)
|
||||
if (!parent) break
|
||||
if (
|
||||
current.type === 'INSTANCE' &&
|
||||
parent.layoutMode !== 'NONE' &&
|
||||
current.layoutPositioning !== 'ABSOLUTE'
|
||||
) {
|
||||
target = current
|
||||
}
|
||||
current = parent
|
||||
}
|
||||
|
||||
return target?.id ?? id
|
||||
}
|
||||
|
||||
function collectMoveOriginals(editor: Editor) {
|
||||
const originals = new Map<string, MoveOriginal>()
|
||||
for (const id of editor.state.selectedIds) {
|
||||
for (const selectedId of editor.state.selectedIds) {
|
||||
const id = autoLayoutMoveTarget(selectedId, editor)
|
||||
const node = editor.graph.getNode(id)
|
||||
if (node) {
|
||||
originals.set(id, {
|
||||
|
|
@ -25,6 +45,17 @@ function collectMoveOriginals(editor: Editor) {
|
|||
return originals
|
||||
}
|
||||
|
||||
function detectDragAutoLayoutParent(originals: Map<string, MoveOriginal>, editor: Editor) {
|
||||
if (originals.size !== 1) return undefined
|
||||
const [id, original] = [...originals][0]
|
||||
const node = editor.graph.getNode(id)
|
||||
const parent = editor.graph.getNode(original.parentId)
|
||||
if (parent && parent.layoutMode !== 'NONE' && node?.layoutPositioning !== 'ABSOLUTE') {
|
||||
return parent.id
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function createSelectionMoveDrag(
|
||||
cx: number,
|
||||
cy: number,
|
||||
|
|
@ -33,13 +64,15 @@ export function createSelectionMoveDrag(
|
|||
): DragState {
|
||||
if (duplicate && editor.state.selectedIds.size > 0) return duplicateAndDrag(cx, cy, editor).drag
|
||||
|
||||
const originals = collectMoveOriginals(editor)
|
||||
|
||||
return {
|
||||
type: 'move',
|
||||
startX: cx,
|
||||
startY: cy,
|
||||
currentX: cx,
|
||||
currentY: cy,
|
||||
originals: collectMoveOriginals(editor),
|
||||
autoLayoutParentId: detectAutoLayoutParent(editor)
|
||||
originals,
|
||||
autoLayoutParentId: detectDragAutoLayoutParent(originals, editor)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
84
tests/e2e/editor/auto-layout-drag.spec.ts
Normal file
84
tests/e2e/editor/auto-layout-drag.spec.ts
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
import { expect, test } from '@playwright/test'
|
||||
|
||||
import { CanvasHelper } from '#tests/helpers/canvas'
|
||||
|
||||
test('dragging selected nested instance content reorders its auto-layout item', async ({ page }) => {
|
||||
await page.goto('/')
|
||||
const canvas = new CanvasHelper(page)
|
||||
await canvas.waitForInit()
|
||||
canvas.errors.length = 0
|
||||
await canvas.clearCanvas()
|
||||
|
||||
const ids = await page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
|
||||
store.state.panX = 0
|
||||
store.state.panY = 0
|
||||
store.state.zoom = 1
|
||||
|
||||
const pageId = store.state.currentPageId
|
||||
const frame = store.graph.createNode('FRAME', pageId, {
|
||||
name: 'Auto Row',
|
||||
x: 100,
|
||||
y: 100,
|
||||
width: 360,
|
||||
height: 80,
|
||||
layoutMode: 'HORIZONTAL',
|
||||
itemSpacing: 20,
|
||||
paddingTop: 10,
|
||||
paddingRight: 10,
|
||||
paddingBottom: 10,
|
||||
paddingLeft: 10
|
||||
})
|
||||
|
||||
const first = store.graph.createNode('INSTANCE', frame.id, {
|
||||
name: 'First item',
|
||||
x: 110,
|
||||
y: 110,
|
||||
width: 120,
|
||||
height: 60
|
||||
})
|
||||
store.graph.createNode('TEXT', first.id, {
|
||||
name: 'First label',
|
||||
text: 'First',
|
||||
x: 8,
|
||||
y: 8,
|
||||
width: 80,
|
||||
height: 24
|
||||
})
|
||||
|
||||
const second = store.graph.createNode('INSTANCE', frame.id, {
|
||||
name: 'Second item',
|
||||
x: 250,
|
||||
y: 110,
|
||||
width: 120,
|
||||
height: 60
|
||||
})
|
||||
const secondText = store.graph.createNode('TEXT', second.id, {
|
||||
name: 'Second label',
|
||||
text: 'Second',
|
||||
x: 8,
|
||||
y: 8,
|
||||
width: 80,
|
||||
height: 24
|
||||
})
|
||||
|
||||
store.select([secondText.id])
|
||||
store.requestRender()
|
||||
return { frame: frame.id, first: first.id, second: second.id, secondText: secondText.id }
|
||||
})
|
||||
await canvas.waitForRender()
|
||||
|
||||
await canvas.drag(370, 228, 230, 228, 12)
|
||||
await canvas.waitForRender()
|
||||
|
||||
const childIds = await page.evaluate((frameId) => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
return store.graph.getNode(frameId)?.childIds ?? []
|
||||
}, ids.frame)
|
||||
|
||||
expect(childIds).toEqual([ids.second, ids.first])
|
||||
canvas.assertNoErrors()
|
||||
})
|
||||
Loading…
Reference in a new issue