Improve row vs column detection to fix any edge cases

This commit is contained in:
Andrew Kingston 2022-10-07 08:46:38 +01:00
parent 9df86362b9
commit fd3662e6b2
3 changed files with 44 additions and 247 deletions

View File

@ -28,7 +28,6 @@
export let isScreen = false export let isScreen = false
export let isBlock = false export let isBlock = false
export let parent = null export let parent = null
export let index = 0
// Get parent contexts // Get parent contexts
const context = getContext("context") const context = getContext("context")
@ -463,14 +462,13 @@
data-name={name} data-name={name}
data-icon={icon} data-icon={icon}
data-parent={parent} data-parent={parent}
data-index={index}
> >
<svelte:component this={constructor} bind:this={ref} {...initialSettings}> <svelte:component this={constructor} bind:this={ref} {...initialSettings}>
{#if hasMissingRequiredSettings} {#if hasMissingRequiredSettings}
<ComponentPlaceholder /> <ComponentPlaceholder />
{:else if children.length} {:else if children.length}
{#each children as child, idx (child._id)} {#each children as child (child._id)}
<svelte:self instance={child} parent={id} index={idx} /> <svelte:self instance={child} parent={id} />
{/each} {/each}
{:else if emptyState} {:else if emptyState}
{#if isScreen} {#if isScreen}

View File

@ -9,32 +9,14 @@
<script> <script>
import { onMount, onDestroy } from "svelte" import { onMount, onDestroy } from "svelte"
import { get } from "svelte/store"
import IndicatorSet from "./IndicatorSet.svelte" import IndicatorSet from "./IndicatorSet.svelte"
import DNDPositionIndicator from "./DNDPositionIndicator.svelte" import { builderStore } from "stores"
import { builderStore, componentStore } from "stores"
import PlaceholderOverlay from "./PlaceholderOverlay.svelte" import PlaceholderOverlay from "./PlaceholderOverlay.svelte"
let dragInfo let dragInfo
let dropInfo let dropInfo
let placeholderInfo 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 getDOMNode = id => {
const component = document.getElementsByClassName(id)[0] const component = document.getElementsByClassName(id)[0]
return [...component.children][0] return [...component.children][0]
@ -93,83 +75,16 @@
} }
e.preventDefault() e.preventDefault()
let { id, parent, node, index, acceptsChildren, empty } = dropInfo let { id, parent, node, acceptsChildren, empty } = dropInfo
const mouseY = e.clientY const mouseY = e.clientY
const mouseX = e.clientX const mouseX = e.clientX
// if (!dropInfo.bounds) { // If we're over something that does not accept children then we go up a
// } else { // level and consider the mouse position relative to the parent
// 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 (!acceptsChildren) { if (!acceptsChildren) {
id = parent id = parent
acceptsChildren = true
empty = false empty = false
node = getDOMNode(parent) 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. // 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 // We're now hovering over something which accepts children and is not
// empty, so we need to work out where to inside the placeholder // empty, so we need to work out where to inside the placeholder
// Calculate the coordinates of various locations on each child.
// Check we're actually inside // We include the placeholder component in this as it guarantees we have
// if ( // at least 2 child components, and therefore guarantee there is no
// mouseY < top || // ambiguity in the layout.
// mouseY > top + height || const childCoords = [...(node.children || [])].map(node => {
// mouseX < left || const bounds = node.children[0].getBoundingClientRect()
// mouseX > left + width return {
// ) { placeholder: node.classList.contains("placeholder"),
// console.log("not inside") centerX: bounds.left + bounds.width / 2,
// return centerY: bounds.top + bounds.height / 2,
// } left: bounds.left,
right: bounds.right,
// Get all DOM nodes of children of this component. top: bounds.top,
// Filter out the placeholder as we don't want it to affect the index of bottom: bounds.bottom,
// 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 variance of X and Y centers to determine layout // Calculate the variance between each set of positions on the children
const xCoords = centers.map(x => x[0]) const variances = Object.keys(childCoords[0]).map(key => {
const yCoords = centers.map(x => x[1]) const coords = childCoords.map(x => x[key])
const xVariance = variance(xCoords) return {
const yVariance = variance(yCoords) variance: variance(coords),
const column = xVariance <= yVariance 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") console.log(column ? "COL" : "ROW")
// Now that we know the layout, find which children in this axis we are // Find the correct index to drop in based on the midpoints of each child
// between // in their primary axis.
const childPositions = column ? yCoords : xCoords // 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 const mousePosition = column ? mouseY : mouseX
let idx = 0 let idx = 0
while (idx < children.length && childPositions[idx] < mousePosition) { while (idx < childPositions.length && childPositions[idx] < mousePosition) {
idx++ idx++
} }
placeholderInfo = { placeholderInfo = {
parent: id, parent: id,
index: idx, 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 // Callback when on top of a component
@ -268,12 +167,6 @@
return 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)") const component = e.target.closest(".component:not(.block)")
if ( if (
component && component &&
@ -285,38 +178,17 @@
return 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 // Precompute and store some info to avoid recalculating everything in
// dragOver // dragOver
dropInfo = { dropInfo = {
id: component.dataset.id, id: component.dataset.id,
parent: component.dataset.parent, parent: component.dataset.parent,
index: parseInt(component.dataset.index),
node: getDOMNode(component.dataset.id), node: getDOMNode(component.dataset.id),
empty: component.classList.contains("empty"), empty: component.classList.contains("empty"),
acceptsChildren: component.classList.contains("parent"), acceptsChildren: component.classList.contains("parent"),
} }
// console.log(
// "enter",
// component.dataset.name,
// "id",
// dropInfo.id,
// "parent",
// dropInfo.parent,
// "index",
// dropInfo.index
// )
handleEvent(e) handleEvent(e)
} else {
// dropInfo = null
} }
} }
@ -380,10 +252,3 @@
{#if $builderStore.isDragging} {#if $builderStore.isDragging}
<PlaceholderOverlay /> <PlaceholderOverlay />
{/if} {/if}
<!--<DNDPositionIndicator-->
<!-- {dropInfo}-->
<!-- color="var(&#45;&#45;spectrum-global-color-static-green-500)"-->
<!-- zIndex="940"-->
<!-- transition-->
<!--/>-->

View File

@ -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}