Don't clear drop target on invalid selection
This commit is contained in:
parent
ee4c5af1c9
commit
428b786184
|
@ -96,6 +96,7 @@
|
||||||
$: selected =
|
$: selected =
|
||||||
$builderStore.inBuilder && $builderStore.selectedComponentId === id
|
$builderStore.inBuilder && $builderStore.selectedComponentId === id
|
||||||
$: inSelectedPath = $componentStore.selectedComponentPath?.includes(id)
|
$: inSelectedPath = $componentStore.selectedComponentPath?.includes(id)
|
||||||
|
$: inDropPath = $componentStore.dropPath?.includes(id)
|
||||||
$: inDragPath = inSelectedPath && $builderStore.editMode
|
$: inDragPath = inSelectedPath && $builderStore.editMode
|
||||||
|
|
||||||
// Derive definition properties which can all be optional, so need to be
|
// 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
|
// Interactive components can be selected, dragged and highlighted inside
|
||||||
// the builder preview
|
// the builder preview
|
||||||
$: builderInteractive =
|
$: builderInteractive =
|
||||||
$builderStore.inBuilder && insideScreenslot && !isBlock
|
$builderStore.inBuilder && insideScreenslot && !isBlock && !instance.static
|
||||||
$: devToolsInteractive = $devToolsStore.allowSelection && !isBlock
|
$: devToolsInteractive = $devToolsStore.allowSelection && !isBlock
|
||||||
$: interactive = builderInteractive || devToolsInteractive
|
$: interactive = builderInteractive || devToolsInteractive
|
||||||
$: editing = editable && selected && $builderStore.editMode
|
$: editing = editable && selected && $builderStore.editMode
|
||||||
|
@ -453,10 +454,11 @@
|
||||||
class:interactive
|
class:interactive
|
||||||
class:editing
|
class:editing
|
||||||
class:block={isBlock}
|
class:block={isBlock}
|
||||||
class:explode={children.length && !isLayout && $builderStore.isDragging}
|
class:explode={children.length && !isLayout && inDropPath && false}
|
||||||
data-id={id}
|
data-id={id}
|
||||||
data-name={name}
|
data-name={name}
|
||||||
data-icon={icon}
|
data-icon={icon}
|
||||||
|
data-placeholder={id === "placeholder"}
|
||||||
>
|
>
|
||||||
<svelte:component this={constructor} bind:this={ref} {...initialSettings}>
|
<svelte:component this={constructor} bind:this={ref} {...initialSettings}>
|
||||||
{#if hasMissingRequiredSettings}
|
{#if hasMissingRequiredSettings}
|
||||||
|
|
|
@ -77,19 +77,16 @@
|
||||||
builderStore.actions.setDragging(false)
|
builderStore.actions.setDragging(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback when on top of a component
|
const validateDrop = (dropInfo, e) => {
|
||||||
const onDragOver = e => {
|
if (!dropInfo) {
|
||||||
// Skip if we aren't validly dragging currently
|
return null
|
||||||
if (!dragInfo || !dropInfo) {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
e.preventDefault()
|
|
||||||
const { droppableInside, bounds } = dropInfo
|
const { droppableInside, bounds } = dropInfo
|
||||||
const { top, left, height, width } = bounds
|
const { top, left, height, width } = bounds
|
||||||
const mouseY = e.clientY
|
const mouseY = e.clientY
|
||||||
const mouseX = e.clientX
|
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 snapLimitV = Math.min(40, height * snapFactor)
|
||||||
const snapLimitH = Math.min(40, width * snapFactor)
|
const snapLimitH = Math.min(40, width * snapFactor)
|
||||||
|
|
||||||
|
@ -108,20 +105,32 @@
|
||||||
|
|
||||||
// When no edges match, drop inside if possible
|
// When no edges match, drop inside if possible
|
||||||
if (!sides.length) {
|
if (!sides.length) {
|
||||||
dropInfo.mode = droppableInside ? "inside" : null
|
if (droppableInside) {
|
||||||
dropInfo.side = null
|
return {
|
||||||
return
|
...dropInfo,
|
||||||
|
mode: "inside",
|
||||||
|
side: null,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// When one edge matches, use that edge
|
// When one edge matches, use that edge
|
||||||
if (sides.length === 1) {
|
if (sides.length === 1) {
|
||||||
dropInfo.side = sides[0]
|
|
||||||
if ([Sides.Top, Sides.Left].includes(sides[0])) {
|
if ([Sides.Top, Sides.Left].includes(sides[0])) {
|
||||||
dropInfo.mode = "above"
|
return {
|
||||||
} else {
|
...dropInfo,
|
||||||
dropInfo.mode = "below"
|
mode: "above",
|
||||||
|
side: sides[0],
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
...dropInfo,
|
||||||
|
mode: "below",
|
||||||
|
side: sides[0],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// When 2 edges match, work out which is closer
|
// When 2 edges match, work out which is closer
|
||||||
|
@ -134,9 +143,34 @@
|
||||||
const edge = delta1 < delta2 ? sides[0] : sides[1]
|
const edge = delta1 < delta2 ? sides[0] : sides[1]
|
||||||
dropInfo.side = edge
|
dropInfo.side = edge
|
||||||
if ([Sides.Top, Sides.Left].includes(edge)) {
|
if ([Sides.Top, Sides.Left].includes(edge)) {
|
||||||
dropInfo.mode = "above"
|
return {
|
||||||
|
...dropInfo,
|
||||||
|
mode: "above",
|
||||||
|
side: edge,
|
||||||
|
}
|
||||||
} else {
|
} 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
|
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)")
|
const element = e.target.closest(".component:not(.block)")
|
||||||
if (
|
if (
|
||||||
element &&
|
element &&
|
||||||
|
@ -172,15 +216,20 @@
|
||||||
// dragOver
|
// dragOver
|
||||||
const child = getDOMNodeForComponent(e.target)
|
const child = getDOMNodeForComponent(e.target)
|
||||||
const bounds = child.getBoundingClientRect()
|
const bounds = child.getBoundingClientRect()
|
||||||
dropInfo = {
|
let nextDropInfo = {
|
||||||
target,
|
target,
|
||||||
name: element.dataset.name,
|
name: element.dataset.name,
|
||||||
icon: element.dataset.icon,
|
icon: element.dataset.icon,
|
||||||
droppableInside: element.classList.contains("empty"),
|
droppableInside: element.classList.contains("empty"),
|
||||||
bounds,
|
bounds,
|
||||||
}
|
}
|
||||||
|
nextDropInfo = validateDrop(nextDropInfo, e)
|
||||||
|
if (nextDropInfo) {
|
||||||
|
console.log("set from enter")
|
||||||
|
dropInfo = nextDropInfo
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
dropInfo = null
|
// dropInfo = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,6 +249,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: mode = dropInfo?.mode
|
||||||
|
$: target = dropInfo?.target
|
||||||
|
$: builderStore.actions.updateDNDPlaceholder(mode, target)
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
// Events fired on the draggable target
|
// Events fired on the draggable target
|
||||||
document.addEventListener("dragstart", onDragStart, false)
|
document.addEventListener("dragstart", onDragStart, false)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { writable, get } from "svelte/store"
|
import { writable, get } from "svelte/store"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { devToolsStore } from "./devTools.js"
|
import { devToolsStore } from "./devTools.js"
|
||||||
|
import { findComponentPathById } from "../utils/components.js"
|
||||||
|
|
||||||
const dispatchEvent = (type, data = {}) => {
|
const dispatchEvent = (type, data = {}) => {
|
||||||
window.parent.postMessage({ type, data })
|
window.parent.postMessage({ type, data })
|
||||||
|
@ -20,6 +21,9 @@ const createBuilderStore = () => {
|
||||||
navigation: null,
|
navigation: null,
|
||||||
hiddenComponentIds: [],
|
hiddenComponentIds: [],
|
||||||
usedPlugins: null,
|
usedPlugins: null,
|
||||||
|
dndMode: null,
|
||||||
|
dndTarget: null,
|
||||||
|
dropTarget: null,
|
||||||
|
|
||||||
// Legacy - allow the builder to specify a layout
|
// Legacy - allow the builder to specify a layout
|
||||||
layout: null,
|
layout: null,
|
||||||
|
@ -102,6 +106,20 @@ const createBuilderStore = () => {
|
||||||
// Notify the builder so we can reload component definitions
|
// Notify the builder so we can reload component definitions
|
||||||
dispatchEvent("reload-plugin")
|
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 {
|
return {
|
||||||
...store,
|
...store,
|
||||||
|
|
|
@ -36,8 +36,13 @@ const createComponentStore = () => {
|
||||||
const definition = getComponentDefinition(component?._component)
|
const definition = getComponentDefinition(component?._component)
|
||||||
|
|
||||||
// Derive the selected component path
|
// Derive the selected component path
|
||||||
const path =
|
const selectedPath =
|
||||||
findComponentPathById(asset?.props, selectedComponentId) || []
|
findComponentPathById(asset?.props, selectedComponentId) || []
|
||||||
|
let dropPath = []
|
||||||
|
if ($builderState.isDragging) {
|
||||||
|
dropPath =
|
||||||
|
findComponentPathById(asset?.props, $builderState.dropTarget) || []
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
customComponentManifest: $store.customComponentManifest,
|
customComponentManifest: $store.customComponentManifest,
|
||||||
|
@ -45,9 +50,10 @@ const createComponentStore = () => {
|
||||||
$store.mountedComponents[selectedComponentId],
|
$store.mountedComponents[selectedComponentId],
|
||||||
selectedComponent: component,
|
selectedComponent: component,
|
||||||
selectedComponentDefinition: definition,
|
selectedComponentDefinition: definition,
|
||||||
selectedComponentPath: path?.map(component => component._id),
|
selectedComponentPath: selectedPath?.map(component => component._id),
|
||||||
mountedComponentCount: Object.keys($store.mountedComponents).length,
|
mountedComponentCount: Object.keys($store.mountedComponents).length,
|
||||||
currentAsset: asset,
|
currentAsset: asset,
|
||||||
|
dropPath: dropPath?.map(component => component._id),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,6 +3,11 @@ import { routeStore } from "./routes"
|
||||||
import { builderStore } from "./builder"
|
import { builderStore } from "./builder"
|
||||||
import { appStore } from "./app"
|
import { appStore } from "./app"
|
||||||
import { RoleUtils } from "@budibase/frontend-core"
|
import { RoleUtils } from "@budibase/frontend-core"
|
||||||
|
import {
|
||||||
|
findComponentById,
|
||||||
|
findComponentPathById,
|
||||||
|
} from "../utils/components.js"
|
||||||
|
import { Helpers } from "@budibase/bbui"
|
||||||
|
|
||||||
const createScreenStore = () => {
|
const createScreenStore = () => {
|
||||||
const store = derived(
|
const store = derived(
|
||||||
|
@ -13,7 +18,7 @@ const createScreenStore = () => {
|
||||||
|
|
||||||
if ($builderStore.inBuilder) {
|
if ($builderStore.inBuilder) {
|
||||||
// Use builder defined definitions if inside the builder preview
|
// Use builder defined definitions if inside the builder preview
|
||||||
activeScreen = $builderStore.screen
|
activeScreen = Helpers.cloneDeep($builderStore.screen)
|
||||||
screens = [activeScreen]
|
screens = [activeScreen]
|
||||||
|
|
||||||
// Legacy - allow the builder to specify a layout
|
// Legacy - allow the builder to specify a layout
|
||||||
|
@ -24,9 +29,11 @@ const createScreenStore = () => {
|
||||||
// Find the correct screen by matching the current route
|
// Find the correct screen by matching the current route
|
||||||
screens = $appStore.screens || []
|
screens = $appStore.screens || []
|
||||||
if ($routeStore.activeRoute) {
|
if ($routeStore.activeRoute) {
|
||||||
activeScreen = screens.find(
|
activeScreen = Helpers.cloneDeep(
|
||||||
|
screens.find(
|
||||||
screen => screen._id === $routeStore.activeRoute.screenId
|
screen => screen._id === $routeStore.activeRoute.screenId
|
||||||
)
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Legacy - find the custom layout for the selected screen
|
// Legacy - find the custom layout for the selected screen
|
||||||
|
@ -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
|
// Assign ranks to screens, preferring higher roles and home screens
|
||||||
screens.forEach(screen => {
|
screens.forEach(screen => {
|
||||||
const roleId = screen.routing.roleId
|
const roleId = screen.routing.roleId
|
||||||
|
|
Loading…
Reference in New Issue