Improve row vs column detection to fix any edge cases
This commit is contained in:
parent
9df86362b9
commit
fd3662e6b2
|
@ -28,7 +28,6 @@
|
|||
export let isScreen = false
|
||||
export let isBlock = false
|
||||
export let parent = null
|
||||
export let index = 0
|
||||
|
||||
// Get parent contexts
|
||||
const context = getContext("context")
|
||||
|
@ -463,14 +462,13 @@
|
|||
data-name={name}
|
||||
data-icon={icon}
|
||||
data-parent={parent}
|
||||
data-index={index}
|
||||
>
|
||||
<svelte:component this={constructor} bind:this={ref} {...initialSettings}>
|
||||
{#if hasMissingRequiredSettings}
|
||||
<ComponentPlaceholder />
|
||||
{:else if children.length}
|
||||
{#each children as child, idx (child._id)}
|
||||
<svelte:self instance={child} parent={id} index={idx} />
|
||||
{#each children as child (child._id)}
|
||||
<svelte:self instance={child} parent={id} />
|
||||
{/each}
|
||||
{:else if emptyState}
|
||||
{#if isScreen}
|
||||
|
|
|
@ -9,32 +9,14 @@
|
|||
|
||||
<script>
|
||||
import { onMount, onDestroy } from "svelte"
|
||||
import { get } from "svelte/store"
|
||||
import IndicatorSet from "./IndicatorSet.svelte"
|
||||
import DNDPositionIndicator from "./DNDPositionIndicator.svelte"
|
||||
import { builderStore, componentStore } from "stores"
|
||||
import { builderStore } from "stores"
|
||||
import PlaceholderOverlay from "./PlaceholderOverlay.svelte"
|
||||
|
||||
let dragInfo
|
||||
let dropInfo
|
||||
let placeholderInfo
|
||||
|
||||
const getEdges = (bounds, mousePoint) => {
|
||||
const { width, height, top, left } = bounds
|
||||
return {
|
||||
[Sides.Top]: [mousePoint[0], top],
|
||||
[Sides.Right]: [left + width, mousePoint[1]],
|
||||
[Sides.Bottom]: [mousePoint[0], top + height],
|
||||
[Sides.Left]: [left, mousePoint[1]],
|
||||
}
|
||||
}
|
||||
|
||||
const calculatePointDelta = (point1, point2) => {
|
||||
const deltaX = Math.abs(point1[0] - point2[0])
|
||||
const deltaY = Math.abs(point1[1] - point2[1])
|
||||
return Math.sqrt(deltaX * deltaX + deltaY * deltaY)
|
||||
}
|
||||
|
||||
const getDOMNode = id => {
|
||||
const component = document.getElementsByClassName(id)[0]
|
||||
return [...component.children][0]
|
||||
|
@ -93,83 +75,16 @@
|
|||
}
|
||||
e.preventDefault()
|
||||
|
||||
let { id, parent, node, index, acceptsChildren, empty } = dropInfo
|
||||
let { id, parent, node, acceptsChildren, empty } = dropInfo
|
||||
const mouseY = e.clientY
|
||||
const mouseX = e.clientX
|
||||
|
||||
// if (!dropInfo.bounds) {
|
||||
// } else {
|
||||
// dropInfo.bounds.top = node.offsetTop
|
||||
// dropInfo.bounds.left = node.offsetLeft
|
||||
// console.log(node.offsetTop)
|
||||
// }
|
||||
|
||||
// console.log("calc")
|
||||
// dropInfo.bounds = bounds
|
||||
|
||||
// If we're over something that does not accept children then we must go
|
||||
// above or below this component
|
||||
// If we're over something that does not accept children then we go up a
|
||||
// level and consider the mouse position relative to the parent
|
||||
if (!acceptsChildren) {
|
||||
id = parent
|
||||
acceptsChildren = true
|
||||
empty = false
|
||||
node = getDOMNode(parent)
|
||||
//
|
||||
//
|
||||
// const bounds = node.getBoundingClientRect()
|
||||
// const { top, left, height, width } = bounds
|
||||
// const snapFactor = 0.5
|
||||
// const snapLimitV = Math.min(40, height * snapFactor)
|
||||
// const snapLimitH = Math.min(40, width * snapFactor)
|
||||
//
|
||||
// // Determine all sides we are within snap range of
|
||||
// let sides = []
|
||||
// if (mouseY <= top + snapLimitV) {
|
||||
// sides.push(Sides.Top)
|
||||
// } else if (mouseY >= top + height - snapLimitV) {
|
||||
// sides.push(Sides.Bottom)
|
||||
// }
|
||||
// if (mouseX < left + snapLimitH) {
|
||||
// sides.push(Sides.Left)
|
||||
// } else if (mouseX > left + width - snapLimitH) {
|
||||
// sides.push(Sides.Right)
|
||||
// }
|
||||
//
|
||||
// // If we're somehow not in range of any side, do nothing
|
||||
// if (!sides.length) {
|
||||
// console.log("no sides match")
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// let side
|
||||
// if (sides.length === 1) {
|
||||
// // When one edge matches, use that edge
|
||||
// side = sides[0]
|
||||
// } else {
|
||||
// // When 2 edges match, work out which is closer
|
||||
// const mousePoint = [mouseX, mouseY]
|
||||
// const edges = getEdges(bounds, mousePoint)
|
||||
// const edge1 = edges[sides[0]]
|
||||
// const delta1 = calculatePointDelta(mousePoint, edge1)
|
||||
// const edge2 = edges[sides[1]]
|
||||
// const delta2 = calculatePointDelta(mousePoint, edge2)
|
||||
// side = delta1 < delta2 ? sides[0] : sides[1]
|
||||
// }
|
||||
// if ([Sides.Top, Sides.Left].includes(side)) {
|
||||
// // Before, so use the current index
|
||||
// console.log("before")
|
||||
// placeholderInfo = {
|
||||
// parent: parent,
|
||||
// index: index,
|
||||
// }
|
||||
// } else {
|
||||
// console.log("after")
|
||||
// // After, so use the next index
|
||||
// placeholderInfo = {
|
||||
// parent: parent,
|
||||
// index: index + 1,
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
// We're now hovering over something which does accept children.
|
||||
|
@ -184,72 +99,56 @@
|
|||
|
||||
// We're now hovering over something which accepts children and is not
|
||||
// empty, so we need to work out where to inside the placeholder
|
||||
|
||||
// Check we're actually inside
|
||||
// if (
|
||||
// mouseY < top ||
|
||||
// mouseY > top + height ||
|
||||
// mouseX < left ||
|
||||
// mouseX > left + width
|
||||
// ) {
|
||||
// console.log("not inside")
|
||||
// return
|
||||
// }
|
||||
|
||||
// Get all DOM nodes of children of this component.
|
||||
// Filter out the placeholder as we don't want it to affect the index of
|
||||
// the new placeholder.
|
||||
const children = [...(node.children || [])]
|
||||
.filter(x => !x.classList.contains("placeholder"))
|
||||
.map(x => x.children[0])
|
||||
|
||||
// Calculate centers of each child
|
||||
const centers = children.map(child => {
|
||||
const childBounds = child.getBoundingClientRect()
|
||||
return [
|
||||
childBounds.left + childBounds.width / 2,
|
||||
childBounds.top + childBounds.height / 2,
|
||||
]
|
||||
// Calculate the coordinates of various locations on each child.
|
||||
// We include the placeholder component in this as it guarantees we have
|
||||
// at least 2 child components, and therefore guarantee there is no
|
||||
// ambiguity in the layout.
|
||||
const childCoords = [...(node.children || [])].map(node => {
|
||||
const bounds = node.children[0].getBoundingClientRect()
|
||||
return {
|
||||
placeholder: node.classList.contains("placeholder"),
|
||||
centerX: bounds.left + bounds.width / 2,
|
||||
centerY: bounds.top + bounds.height / 2,
|
||||
left: bounds.left,
|
||||
right: bounds.right,
|
||||
top: bounds.top,
|
||||
bottom: bounds.bottom,
|
||||
}
|
||||
})
|
||||
|
||||
// Calculate variance of X and Y centers to determine layout
|
||||
const xCoords = centers.map(x => x[0])
|
||||
const yCoords = centers.map(x => x[1])
|
||||
const xVariance = variance(xCoords)
|
||||
const yVariance = variance(yCoords)
|
||||
const column = xVariance <= yVariance
|
||||
// Calculate the variance between each set of positions on the children
|
||||
const variances = Object.keys(childCoords[0]).map(key => {
|
||||
const coords = childCoords.map(x => x[key])
|
||||
return {
|
||||
variance: variance(coords),
|
||||
side: key,
|
||||
}
|
||||
})
|
||||
|
||||
// Sort by variance. The lowest variance position indicates whether we are
|
||||
// in a row or column layout
|
||||
variances.sort((a, b) => {
|
||||
return a.variance < b.variance ? -1 : 1
|
||||
})
|
||||
const column = ["centerX", "left", "right"].includes(variances[0].side)
|
||||
console.log(column ? "COL" : "ROW")
|
||||
|
||||
// Now that we know the layout, find which children in this axis we are
|
||||
// between
|
||||
const childPositions = column ? yCoords : xCoords
|
||||
// Find the correct index to drop in based on the midpoints of each child
|
||||
// in their primary axis.
|
||||
// Here we filter out the placeholder component as we do not want it to
|
||||
// affect the determination of the new index.
|
||||
const childPositions = column
|
||||
? childCoords.filter(x => !x.placeholder).map(x => x.centerY)
|
||||
: childCoords.filter(x => !x.placeholder).map(x => x.centerX)
|
||||
const mousePosition = column ? mouseY : mouseX
|
||||
|
||||
let idx = 0
|
||||
while (idx < children.length && childPositions[idx] < mousePosition) {
|
||||
while (idx < childPositions.length && childPositions[idx] < mousePosition) {
|
||||
idx++
|
||||
}
|
||||
|
||||
placeholderInfo = {
|
||||
parent: id,
|
||||
index: idx,
|
||||
}
|
||||
// // When no edges match, drop inside if possible
|
||||
// if (!sides.length) {
|
||||
// if (empty) {
|
||||
// console.log("allowed inside")
|
||||
// return {
|
||||
// ...dropInfo,
|
||||
// mode: "inside",
|
||||
// side: null,
|
||||
// bounds,
|
||||
// }
|
||||
// } else {
|
||||
// // No sides but also not empty?
|
||||
// console.log("no sides match, but not empty")
|
||||
// return null
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
// Callback when on top of a component
|
||||
|
@ -268,12 +167,6 @@
|
|||
return
|
||||
}
|
||||
|
||||
// Do nothing if this is the placeholder
|
||||
// if (element.dataset.id === "placeholder") {
|
||||
// console.log("placeholder")
|
||||
// return
|
||||
// }
|
||||
|
||||
const component = e.target.closest(".component:not(.block)")
|
||||
if (
|
||||
component &&
|
||||
|
@ -285,38 +178,17 @@
|
|||
return
|
||||
}
|
||||
|
||||
// Ensure the dragging flag is always set.
|
||||
// There's a bit of a race condition between the app reinitialisation
|
||||
// after selecting the DND component and setting this the first time
|
||||
// if (!get(builderStore).isDragging) {
|
||||
// builderStore.actions.setDragging(true)
|
||||
// }
|
||||
|
||||
// Precompute and store some info to avoid recalculating everything in
|
||||
// dragOver
|
||||
dropInfo = {
|
||||
id: component.dataset.id,
|
||||
parent: component.dataset.parent,
|
||||
index: parseInt(component.dataset.index),
|
||||
node: getDOMNode(component.dataset.id),
|
||||
empty: component.classList.contains("empty"),
|
||||
acceptsChildren: component.classList.contains("parent"),
|
||||
}
|
||||
|
||||
// console.log(
|
||||
// "enter",
|
||||
// component.dataset.name,
|
||||
// "id",
|
||||
// dropInfo.id,
|
||||
// "parent",
|
||||
// dropInfo.parent,
|
||||
// "index",
|
||||
// dropInfo.index
|
||||
// )
|
||||
|
||||
handleEvent(e)
|
||||
} else {
|
||||
// dropInfo = null
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -380,10 +252,3 @@
|
|||
{#if $builderStore.isDragging}
|
||||
<PlaceholderOverlay />
|
||||
{/if}
|
||||
|
||||
<!--<DNDPositionIndicator-->
|
||||
<!-- {dropInfo}-->
|
||||
<!-- color="var(--spectrum-global-color-static-green-500)"-->
|
||||
<!-- zIndex="940"-->
|
||||
<!-- transition-->
|
||||
<!--/>-->
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
<script>
|
||||
import Indicator from "./Indicator.svelte"
|
||||
import { Sides } from "./DNDHandler.svelte"
|
||||
|
||||
export let dropInfo
|
||||
export let zIndex
|
||||
export let color
|
||||
export let transition
|
||||
|
||||
$: dimensions = getDimensions(dropInfo)
|
||||
$: prefix = dropInfo?.mode === "above" ? "Before" : "After"
|
||||
$: text = `${prefix} ${dropInfo?.name}`
|
||||
$: icon = dropInfo?.icon
|
||||
$: renderKey = `${dropInfo?.target}-${dropInfo?.side}`
|
||||
|
||||
const getDimensions = info => {
|
||||
const { bounds, side } = info ?? {}
|
||||
if (!bounds || !side) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Get preview offset
|
||||
const root = document.getElementById("clip-root")
|
||||
const rootBounds = root.getBoundingClientRect()
|
||||
|
||||
// Subtract preview offset from bounds
|
||||
let { left, top, width, height } = bounds
|
||||
left -= rootBounds.left
|
||||
top -= rootBounds.top
|
||||
|
||||
// Determine position
|
||||
if (side === Sides.Top || side === Sides.Bottom) {
|
||||
return {
|
||||
top: side === Sides.Top ? top - 4 : top + height,
|
||||
left: left - 2,
|
||||
width: width + 4,
|
||||
height: 0,
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
top: top - 2,
|
||||
left: side === Sides.Left ? left - 4 : left + width,
|
||||
width: 0,
|
||||
height: height + 4,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#key renderKey}
|
||||
{#if dimensions && dropInfo?.mode !== "inside"}
|
||||
<Indicator
|
||||
left={Math.round(dimensions.left)}
|
||||
top={Math.round(dimensions.top)}
|
||||
width={dimensions.width}
|
||||
height={dimensions.height}
|
||||
{text}
|
||||
{icon}
|
||||
{zIndex}
|
||||
{color}
|
||||
{transition}
|
||||
alignRight={dropInfo?.side === Sides.Right}
|
||||
line
|
||||
/>
|
||||
{/if}
|
||||
{/key}
|
Loading…
Reference in New Issue