Don't clear drop target on invalid selection

This commit is contained in:
Andrew Kingston 2022-10-06 09:17:26 +01:00
parent a6802e921f
commit 9c82a9d073
5 changed files with 139 additions and 25 deletions

View File

@ -96,6 +96,7 @@
$: selected =
$builderStore.inBuilder && $builderStore.selectedComponentId === id
$: inSelectedPath = $componentStore.selectedComponentPath?.includes(id)
$: inDropPath = $componentStore.dropPath?.includes(id)
$: inDragPath = inSelectedPath && $builderStore.editMode
// Derive definition properties which can all be optional, so need to be
@ -108,7 +109,7 @@
// Interactive components can be selected, dragged and highlighted inside
// the builder preview
$: builderInteractive =
$builderStore.inBuilder && insideScreenslot && !isBlock
$builderStore.inBuilder && insideScreenslot && !isBlock && !instance.static
$: devToolsInteractive = $devToolsStore.allowSelection && !isBlock
$: interactive = builderInteractive || devToolsInteractive
$: editing = editable && selected && $builderStore.editMode
@ -453,10 +454,11 @@
class:interactive
class:editing
class:block={isBlock}
class:explode={children.length && !isLayout && $builderStore.isDragging}
class:explode={children.length && !isLayout && inDropPath && false}
data-id={id}
data-name={name}
data-icon={icon}
data-placeholder={id === "placeholder"}
>
<svelte:component this={constructor} bind:this={ref} {...initialSettings}>
{#if hasMissingRequiredSettings}

View File

@ -77,19 +77,16 @@
builderStore.actions.setDragging(false)
}
// Callback when on top of a component
const onDragOver = e => {
// Skip if we aren't validly dragging currently
if (!dragInfo || !dropInfo) {
return
const validateDrop = (dropInfo, e) => {
if (!dropInfo) {
return null
}
e.preventDefault()
const { droppableInside, bounds } = dropInfo
const { top, left, height, width } = bounds
const mouseY = e.clientY
const mouseX = e.clientX
const snapFactor = droppableInside ? 0.33 : 0.5
const snapFactor = droppableInside ? 0.25 : 0.5
const snapLimitV = Math.min(40, height * snapFactor)
const snapLimitH = Math.min(40, width * snapFactor)
@ -108,20 +105,32 @@
// When no edges match, drop inside if possible
if (!sides.length) {
dropInfo.mode = droppableInside ? "inside" : null
dropInfo.side = null
return
if (droppableInside) {
return {
...dropInfo,
mode: "inside",
side: null,
}
} else {
return null
}
}
// When one edge matches, use that edge
if (sides.length === 1) {
dropInfo.side = sides[0]
if ([Sides.Top, Sides.Left].includes(sides[0])) {
dropInfo.mode = "above"
return {
...dropInfo,
mode: "above",
side: sides[0],
}
} else {
dropInfo.mode = "below"
return {
...dropInfo,
mode: "below",
side: sides[0],
}
}
return
}
// When 2 edges match, work out which is closer
@ -134,9 +143,34 @@
const edge = delta1 < delta2 ? sides[0] : sides[1]
dropInfo.side = edge
if ([Sides.Top, Sides.Left].includes(edge)) {
dropInfo.mode = "above"
return {
...dropInfo,
mode: "above",
side: edge,
}
} else {
dropInfo.mode = "below"
return {
...dropInfo,
mode: "below",
side: edge,
}
}
}
// Callback when on top of a component
const onDragOver = e => {
// Skip if we aren't validly dragging currently
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)
}
}
@ -147,6 +181,16 @@
return
}
// Update drop target
const dropTarget = e.target.closest(".component")
builderStore.actions.setDropTarget(dropTarget?.dataset.id)
// // Do nothing if this is the placeholder
// if (element.dataset.id === "placeholder") {
// console.log("placeholder")
// return
// }
const element = e.target.closest(".component:not(.block)")
if (
element &&
@ -172,15 +216,20 @@
// dragOver
const child = getDOMNodeForComponent(e.target)
const bounds = child.getBoundingClientRect()
dropInfo = {
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
}
} else {
dropInfo = null
// dropInfo = null
}
}
@ -200,6 +249,10 @@
}
}
$: mode = dropInfo?.mode
$: target = dropInfo?.target
$: builderStore.actions.updateDNDPlaceholder(mode, target)
onMount(() => {
// Events fired on the draggable target
document.addEventListener("dragstart", onDragStart, false)

View File

@ -1,6 +1,7 @@
import { writable, get } from "svelte/store"
import { API } from "api"
import { devToolsStore } from "./devTools.js"
import { findComponentPathById } from "../utils/components.js"
const dispatchEvent = (type, data = {}) => {
window.parent.postMessage({ type, data })
@ -20,6 +21,9 @@ const createBuilderStore = () => {
navigation: null,
hiddenComponentIds: [],
usedPlugins: null,
dndMode: null,
dndTarget: null,
dropTarget: null,
// Legacy - allow the builder to specify a layout
layout: null,
@ -102,6 +106,20 @@ const createBuilderStore = () => {
// Notify the builder so we can reload component definitions
dispatchEvent("reload-plugin")
},
updateDNDPlaceholder: (mode, target) => {
console.log(mode, target)
store.update(state => {
state.dndMode = mode
state.dndTarget = target
return state
})
},
setDropTarget: target => {
store.update(state => {
state.dropTarget = target
return state
})
},
}
return {
...store,

View File

@ -36,8 +36,13 @@ const createComponentStore = () => {
const definition = getComponentDefinition(component?._component)
// Derive the selected component path
const path =
const selectedPath =
findComponentPathById(asset?.props, selectedComponentId) || []
let dropPath = []
if ($builderState.isDragging) {
dropPath =
findComponentPathById(asset?.props, $builderState.dropTarget) || []
}
return {
customComponentManifest: $store.customComponentManifest,
@ -45,9 +50,10 @@ const createComponentStore = () => {
$store.mountedComponents[selectedComponentId],
selectedComponent: component,
selectedComponentDefinition: definition,
selectedComponentPath: path?.map(component => component._id),
selectedComponentPath: selectedPath?.map(component => component._id),
mountedComponentCount: Object.keys($store.mountedComponents).length,
currentAsset: asset,
dropPath: dropPath?.map(component => component._id),
}
}
)

View File

@ -3,6 +3,11 @@ import { routeStore } from "./routes"
import { builderStore } from "./builder"
import { appStore } from "./app"
import { RoleUtils } from "@budibase/frontend-core"
import {
findComponentById,
findComponentPathById,
} from "../utils/components.js"
import { Helpers } from "@budibase/bbui"
const createScreenStore = () => {
const store = derived(
@ -13,7 +18,7 @@ const createScreenStore = () => {
if ($builderStore.inBuilder) {
// Use builder defined definitions if inside the builder preview
activeScreen = $builderStore.screen
activeScreen = Helpers.cloneDeep($builderStore.screen)
screens = [activeScreen]
// Legacy - allow the builder to specify a layout
@ -24,8 +29,10 @@ const createScreenStore = () => {
// Find the correct screen by matching the current route
screens = $appStore.screens || []
if ($routeStore.activeRoute) {
activeScreen = screens.find(
screen => screen._id === $routeStore.activeRoute.screenId
activeScreen = Helpers.cloneDeep(
screens.find(
screen => screen._id === $routeStore.activeRoute.screenId
)
)
}
@ -40,6 +47,34 @@ 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 placeholder = {
...selectedComponent,
_id: "placeholder",
static: true,
}
// delete selectedComponent._component
if (dndMode === "inside") {
const target = findComponentById(activeScreen.props, dndTarget)
target._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)
}
}
}
// Assign ranks to screens, preferring higher roles and home screens
screens.forEach(screen => {
const roleId = screen.routing.roleId