Rewrite drag and drop from scratch using mouse position heuristics
This commit is contained in:
parent
9c82a9d073
commit
bb8388133a
|
@ -27,6 +27,8 @@
|
|||
export let isLayout = false
|
||||
export let isScreen = false
|
||||
export let isBlock = false
|
||||
export let parent = null
|
||||
export let index = 0
|
||||
|
||||
// Get parent contexts
|
||||
const context = getContext("context")
|
||||
|
@ -96,7 +98,7 @@
|
|||
$: selected =
|
||||
$builderStore.inBuilder && $builderStore.selectedComponentId === id
|
||||
$: inSelectedPath = $componentStore.selectedComponentPath?.includes(id)
|
||||
$: inDropPath = $componentStore.dropPath?.includes(id)
|
||||
$: isDndParent = $componentStore.dndParent === id
|
||||
$: inDragPath = inSelectedPath && $builderStore.editMode
|
||||
|
||||
// Derive definition properties which can all be optional, so need to be
|
||||
|
@ -119,7 +121,7 @@
|
|||
!isLayout &&
|
||||
!isScreen &&
|
||||
definition?.draggable !== false
|
||||
$: droppable = interactive && !isLayout && !isScreen
|
||||
$: droppable = interactive
|
||||
$: builderHidden =
|
||||
$builderStore.inBuilder && $builderStore.hiddenComponentIds?.includes(id)
|
||||
|
||||
|
@ -451,21 +453,24 @@
|
|||
class:draggable
|
||||
class:droppable
|
||||
class:empty
|
||||
class:parent={hasChildren}
|
||||
class:interactive
|
||||
class:editing
|
||||
class:block={isBlock}
|
||||
class:explode={children.length && !isLayout && inDropPath && false}
|
||||
class:explode={interactive && hasChildren && $builderStore.isDragging}
|
||||
class:placeholder={id === "placeholder"}
|
||||
data-id={id}
|
||||
data-name={name}
|
||||
data-icon={icon}
|
||||
data-placeholder={id === "placeholder"}
|
||||
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 (child._id)}
|
||||
<svelte:self instance={child} />
|
||||
{#each children as child, idx (child._id)}
|
||||
<svelte:self instance={child} parent={id} index={idx} />
|
||||
{/each}
|
||||
{:else if emptyState}
|
||||
{#if isScreen}
|
||||
|
@ -488,8 +493,9 @@
|
|||
transition: padding 250ms ease, border 250ms ease;
|
||||
}
|
||||
.component.explode :global(> *) {
|
||||
padding: 12px 4px !important;
|
||||
padding: 16px !important;
|
||||
border: 2px dashed var(--spectrum-global-color-gray-400) !important;
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
.interactive :global(*:hover) {
|
||||
cursor: pointer;
|
||||
|
|
|
@ -12,10 +12,12 @@
|
|||
import { get } from "svelte/store"
|
||||
import IndicatorSet from "./IndicatorSet.svelte"
|
||||
import DNDPositionIndicator from "./DNDPositionIndicator.svelte"
|
||||
import { builderStore } from "stores"
|
||||
import { builderStore, componentStore } from "stores"
|
||||
import PlaceholderOverlay from "./PlaceholderOverlay.svelte"
|
||||
|
||||
let dragInfo
|
||||
let dropInfo
|
||||
let placeholderInfo
|
||||
|
||||
const getEdges = (bounds, mousePoint) => {
|
||||
const { width, height, top, left } = bounds
|
||||
|
@ -33,39 +35,37 @@
|
|||
return Math.sqrt(deltaX * deltaX + deltaY * deltaY)
|
||||
}
|
||||
|
||||
const getDOMNodeForComponent = component => {
|
||||
const parent = component.closest(".component")
|
||||
const children = Array.from(parent.children)
|
||||
return children[0]
|
||||
const getDOMNode = id => {
|
||||
const component = document.getElementsByClassName(id)[0]
|
||||
return [...component.children][0]
|
||||
}
|
||||
|
||||
// Callback when initially starting a drag on a draggable component
|
||||
const onDragStart = e => {
|
||||
const parent = e.target.closest(".component")
|
||||
if (!parent?.classList.contains("draggable")) {
|
||||
const component = e.target.closest(".component")
|
||||
if (!component?.classList.contains("draggable")) {
|
||||
return
|
||||
}
|
||||
|
||||
// Update state
|
||||
dragInfo = {
|
||||
target: parent.dataset.id,
|
||||
parent: parent.dataset.parent,
|
||||
target: component.dataset.id,
|
||||
}
|
||||
builderStore.actions.selectComponent(dragInfo.target)
|
||||
builderStore.actions.setDragging(true)
|
||||
|
||||
// Highlight being dragged by setting opacity
|
||||
const child = getDOMNodeForComponent(e.target)
|
||||
const child = getDOMNode(component.dataset.id)
|
||||
if (child) {
|
||||
child.style.opacity = "0.5"
|
||||
}
|
||||
}
|
||||
|
||||
// Callback when drag stops (whether dropped or not)
|
||||
const onDragEnd = e => {
|
||||
const onDragEnd = () => {
|
||||
// Reset opacity style
|
||||
if (dragInfo) {
|
||||
const child = getDOMNodeForComponent(e.target)
|
||||
const child = getDOMNode(dragInfo.target)
|
||||
if (child) {
|
||||
child.style.opacity = ""
|
||||
}
|
||||
|
@ -77,84 +77,179 @@
|
|||
builderStore.actions.setDragging(false)
|
||||
}
|
||||
|
||||
const validateDrop = (dropInfo, e) => {
|
||||
const variance = arr => {
|
||||
const mean = arr.reduce((a, b) => a + b, 0) / arr.length
|
||||
let squareSum = 0
|
||||
arr.forEach(value => {
|
||||
const delta = value - mean
|
||||
squareSum += delta * delta
|
||||
})
|
||||
return squareSum / arr.length
|
||||
}
|
||||
|
||||
const handleEvent = e => {
|
||||
if (!dropInfo) {
|
||||
return null
|
||||
}
|
||||
e.preventDefault()
|
||||
|
||||
const { droppableInside, bounds } = dropInfo
|
||||
const { top, left, height, width } = bounds
|
||||
let { id, parent, node, index, acceptsChildren, empty } = dropInfo
|
||||
const mouseY = e.clientY
|
||||
const mouseX = e.clientX
|
||||
const snapFactor = droppableInside ? 0.25 : 0.5
|
||||
const snapLimitV = Math.min(40, height * snapFactor)
|
||||
const snapLimitH = Math.min(40, width * snapFactor)
|
||||
|
||||
// Determine all sies 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 (!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 (!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,
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
// When no edges match, drop inside if possible
|
||||
if (!sides.length) {
|
||||
if (droppableInside) {
|
||||
return {
|
||||
...dropInfo,
|
||||
mode: "inside",
|
||||
side: null,
|
||||
}
|
||||
} else {
|
||||
return null
|
||||
// We're now hovering over something which does accept children.
|
||||
// If it is empty, just go inside it
|
||||
if (empty) {
|
||||
placeholderInfo = {
|
||||
parent: id,
|
||||
index: 0,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// When one edge matches, use that edge
|
||||
if (sides.length === 1) {
|
||||
if ([Sides.Top, Sides.Left].includes(sides[0])) {
|
||||
return {
|
||||
...dropInfo,
|
||||
mode: "above",
|
||||
side: sides[0],
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
...dropInfo,
|
||||
mode: "below",
|
||||
side: sides[0],
|
||||
}
|
||||
}
|
||||
// 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 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
|
||||
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
|
||||
const mousePosition = column ? mouseY : mouseX
|
||||
|
||||
let idx = 0
|
||||
while (idx < children.length && childPositions[idx] < mousePosition) {
|
||||
idx++
|
||||
}
|
||||
|
||||
// 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)
|
||||
const edge = delta1 < delta2 ? sides[0] : sides[1]
|
||||
dropInfo.side = edge
|
||||
if ([Sides.Top, Sides.Left].includes(edge)) {
|
||||
return {
|
||||
...dropInfo,
|
||||
mode: "above",
|
||||
side: edge,
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
...dropInfo,
|
||||
mode: "below",
|
||||
side: edge,
|
||||
}
|
||||
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
|
||||
|
@ -163,15 +258,7 @@
|
|||
if (!dragInfo || !dropInfo) {
|
||||
return
|
||||
}
|
||||
|
||||
e.preventDefault()
|
||||
|
||||
const nextDropInfo = validateDrop(dropInfo, e)
|
||||
if (nextDropInfo) {
|
||||
console.log("set from over")
|
||||
dropInfo = nextDropInfo
|
||||
console.log(dropInfo.mode, dropInfo.target)
|
||||
}
|
||||
handleEvent(e)
|
||||
}
|
||||
|
||||
// Callback when entering a potential drop target
|
||||
|
@ -181,53 +268,53 @@
|
|||
return
|
||||
}
|
||||
|
||||
// Update drop target
|
||||
const dropTarget = e.target.closest(".component")
|
||||
builderStore.actions.setDropTarget(dropTarget?.dataset.id)
|
||||
|
||||
// // Do nothing if this is the placeholder
|
||||
// Do nothing if this is the placeholder
|
||||
// if (element.dataset.id === "placeholder") {
|
||||
// console.log("placeholder")
|
||||
// return
|
||||
// }
|
||||
|
||||
const element = e.target.closest(".component:not(.block)")
|
||||
const component = e.target.closest(".component:not(.block)")
|
||||
if (
|
||||
element &&
|
||||
element.classList.contains("droppable") &&
|
||||
element.dataset.id !== dragInfo.target
|
||||
component &&
|
||||
component.classList.contains("droppable") &&
|
||||
component.dataset.id !== dragInfo.target
|
||||
) {
|
||||
// Do nothing if this is the same target
|
||||
if (element.dataset.id === dropInfo?.target) {
|
||||
if (component.dataset.id === dropInfo?.target) {
|
||||
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)
|
||||
}
|
||||
|
||||
// Store target ID
|
||||
const target = element.dataset.id
|
||||
// if (!get(builderStore).isDragging) {
|
||||
// builderStore.actions.setDragging(true)
|
||||
// }
|
||||
|
||||
// Precompute and store some info to avoid recalculating everything in
|
||||
// dragOver
|
||||
const child = getDOMNodeForComponent(e.target)
|
||||
const bounds = child.getBoundingClientRect()
|
||||
let nextDropInfo = {
|
||||
target,
|
||||
name: element.dataset.name,
|
||||
icon: element.dataset.icon,
|
||||
droppableInside: element.classList.contains("empty"),
|
||||
bounds,
|
||||
}
|
||||
nextDropInfo = validateDrop(nextDropInfo, e)
|
||||
if (nextDropInfo) {
|
||||
console.log("set from enter")
|
||||
dropInfo = nextDropInfo
|
||||
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
|
||||
}
|
||||
|
@ -240,18 +327,22 @@
|
|||
// Callback when dropping a drag on top of some component
|
||||
const onDrop = e => {
|
||||
e.preventDefault()
|
||||
dropInfo = null
|
||||
placeholderInfo = null
|
||||
dragInfo = null
|
||||
builderStore.actions.setDragging(false)
|
||||
if (dropInfo?.mode) {
|
||||
builderStore.actions.moveComponent(
|
||||
dragInfo.target,
|
||||
dropInfo.target,
|
||||
dropInfo.mode
|
||||
)
|
||||
// builderStore.actions.moveComponent(
|
||||
// dragInfo.target,
|
||||
// dropInfo.target,
|
||||
// dropInfo.mode
|
||||
// )
|
||||
}
|
||||
}
|
||||
|
||||
$: mode = dropInfo?.mode
|
||||
$: target = dropInfo?.target
|
||||
$: builderStore.actions.updateDNDPlaceholder(mode, target)
|
||||
$: parent = placeholderInfo?.parent
|
||||
$: index = placeholderInfo?.index
|
||||
$: builderStore.actions.updateDNDPlaceholder(parent, index)
|
||||
|
||||
onMount(() => {
|
||||
// Events fired on the draggable target
|
||||
|
@ -279,16 +370,20 @@
|
|||
</script>
|
||||
|
||||
<IndicatorSet
|
||||
componentId={dropInfo?.mode === "inside" ? dropInfo.target : null}
|
||||
componentId={$builderStore.dndParent}
|
||||
color="var(--spectrum-global-color-static-green-500)"
|
||||
zIndex="930"
|
||||
transition
|
||||
prefix="Inside"
|
||||
/>
|
||||
|
||||
<DNDPositionIndicator
|
||||
{dropInfo}
|
||||
color="var(--spectrum-global-color-static-green-500)"
|
||||
zIndex="940"
|
||||
transition
|
||||
/>
|
||||
{#if $builderStore.isDragging}
|
||||
<PlaceholderOverlay />
|
||||
{/if}
|
||||
|
||||
<!--<DNDPositionIndicator-->
|
||||
<!-- {dropInfo}-->
|
||||
<!-- color="var(--spectrum-global-color-static-green-500)"-->
|
||||
<!-- zIndex="940"-->
|
||||
<!-- transition-->
|
||||
<!--/>-->
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<div id="placeholder" class="placeholder" />
|
||||
|
||||
<style>
|
||||
.placeholder {
|
||||
display: block;
|
||||
min-height: 64px;
|
||||
min-width: 64px;
|
||||
flex: 0 0 64px;
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,43 @@
|
|||
<script>
|
||||
import { onMount } from "svelte"
|
||||
|
||||
let left, top, height, width
|
||||
|
||||
onMount(() => {
|
||||
const interval = setInterval(() => {
|
||||
const node = document.getElementById("placeholder")
|
||||
if (!node) {
|
||||
height = 0
|
||||
width = 0
|
||||
} else {
|
||||
const bounds = node.getBoundingClientRect()
|
||||
left = bounds.left
|
||||
top = bounds.top
|
||||
height = bounds.height
|
||||
width = bounds.width
|
||||
}
|
||||
}, 100)
|
||||
|
||||
return () => {
|
||||
clearInterval(interval)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if left != null}
|
||||
<div
|
||||
class="overlay"
|
||||
style="left: {left}px; top: {top}px; width: {width}px; height: {height}px;"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.overlay {
|
||||
position: fixed;
|
||||
z-index: 800;
|
||||
background: hsl(160, 64%, 90%);
|
||||
border-radius: 4px;
|
||||
transition: all 130ms ease-out;
|
||||
border: 2px solid var(--spectrum-global-color-static-green-500);
|
||||
}
|
||||
</style>
|
|
@ -24,6 +24,7 @@ let app
|
|||
const loadBudibase = async () => {
|
||||
// Update builder store with any builder flags
|
||||
builderStore.set({
|
||||
...get(builderStore),
|
||||
inBuilder: !!window["##BUDIBASE_IN_BUILDER##"],
|
||||
layout: window["##BUDIBASE_PREVIEW_LAYOUT##"],
|
||||
screen: window["##BUDIBASE_PREVIEW_SCREEN##"],
|
||||
|
|
|
@ -21,9 +21,9 @@ const createBuilderStore = () => {
|
|||
navigation: null,
|
||||
hiddenComponentIds: [],
|
||||
usedPlugins: null,
|
||||
dndMode: null,
|
||||
dndTarget: null,
|
||||
dropTarget: null,
|
||||
|
||||
dndParent: null,
|
||||
dndIndex: null,
|
||||
|
||||
// Legacy - allow the builder to specify a layout
|
||||
layout: null,
|
||||
|
@ -106,17 +106,10 @@ const createBuilderStore = () => {
|
|||
// Notify the builder so we can reload component definitions
|
||||
dispatchEvent("reload-plugin")
|
||||
},
|
||||
updateDNDPlaceholder: (mode, target) => {
|
||||
console.log(mode, target)
|
||||
updateDNDPlaceholder: (parent, index) => {
|
||||
store.update(state => {
|
||||
state.dndMode = mode
|
||||
state.dndTarget = target
|
||||
return state
|
||||
})
|
||||
},
|
||||
setDropTarget: target => {
|
||||
store.update(state => {
|
||||
state.dropTarget = target
|
||||
state.dndParent = parent
|
||||
state.dndIndex = index
|
||||
return state
|
||||
})
|
||||
},
|
||||
|
|
|
@ -5,6 +5,7 @@ import { devToolsStore } from "./devTools"
|
|||
import { screenStore } from "./screens"
|
||||
import { builderStore } from "./builder"
|
||||
import Router from "../components/Router.svelte"
|
||||
import Placeholder from "../components/preview/Placeholder.svelte"
|
||||
import * as AppComponents from "../components/app/index.js"
|
||||
|
||||
const budibasePrefix = "@budibase/standard-components/"
|
||||
|
@ -38,11 +39,6 @@ const createComponentStore = () => {
|
|||
// Derive the selected component path
|
||||
const selectedPath =
|
||||
findComponentPathById(asset?.props, selectedComponentId) || []
|
||||
let dropPath = []
|
||||
if ($builderState.isDragging) {
|
||||
dropPath =
|
||||
findComponentPathById(asset?.props, $builderState.dropTarget) || []
|
||||
}
|
||||
|
||||
return {
|
||||
customComponentManifest: $store.customComponentManifest,
|
||||
|
@ -53,7 +49,6 @@ const createComponentStore = () => {
|
|||
selectedComponentPath: selectedPath?.map(component => component._id),
|
||||
mountedComponentCount: Object.keys($store.mountedComponents).length,
|
||||
currentAsset: asset,
|
||||
dropPath: dropPath?.map(component => component._id),
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -113,6 +108,8 @@ const createComponentStore = () => {
|
|||
// Screenslot is an edge case
|
||||
if (type === "screenslot") {
|
||||
type = `${budibasePrefix}${type}`
|
||||
} else if (type === "placeholder") {
|
||||
return {}
|
||||
}
|
||||
|
||||
// Handle built-in components
|
||||
|
@ -132,6 +129,8 @@ const createComponentStore = () => {
|
|||
}
|
||||
if (type === "screenslot") {
|
||||
return Router
|
||||
} else if (type === "placeholder") {
|
||||
return Placeholder
|
||||
}
|
||||
|
||||
// Handle budibase components
|
||||
|
|
|
@ -48,30 +48,24 @@ const createScreenStore = () => {
|
|||
}
|
||||
|
||||
// Insert DND placeholder if required
|
||||
const { dndTarget, dndMode, selectedComponentId } = $builderStore
|
||||
const insert = false
|
||||
if (insert && activeScreen && dndTarget && dndMode) {
|
||||
let selectedComponent = findComponentById(
|
||||
activeScreen.props,
|
||||
selectedComponentId
|
||||
)
|
||||
const { dndParent, dndIndex } = $builderStore
|
||||
const insert = true
|
||||
if (insert && activeScreen && dndParent && dndIndex != null) {
|
||||
// let selectedComponent = findComponentById(
|
||||
// activeScreen.props,
|
||||
// selectedComponentId
|
||||
// )
|
||||
// delete selectedComponent._component
|
||||
const placeholder = {
|
||||
...selectedComponent,
|
||||
_component: "placeholder",
|
||||
_id: "placeholder",
|
||||
static: true,
|
||||
}
|
||||
// delete selectedComponent._component
|
||||
if (dndMode === "inside") {
|
||||
const target = findComponentById(activeScreen.props, dndTarget)
|
||||
target._children = [placeholder]
|
||||
let parent = findComponentById(activeScreen.props, dndParent)
|
||||
if (!parent._children?.length) {
|
||||
parent._children = [placeholder]
|
||||
} else {
|
||||
const path = findComponentPathById(activeScreen.props, dndTarget)
|
||||
const parent = path?.[path.length - 2]
|
||||
if (parent) {
|
||||
const idx = parent._children.findIndex(x => x._id === dndTarget)
|
||||
const delta = dndMode === "below" ? 1 : -1
|
||||
parent._children.splice(idx + delta, 0, placeholder)
|
||||
}
|
||||
parent._children.splice(dndIndex, 0, placeholder)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue