Improve DND experience, use correct size of drop placeholder and don't drop if the position is unchanged
This commit is contained in:
parent
f2d1730139
commit
b5d50a71d7
|
@ -22,6 +22,7 @@
|
|||
import Placeholder from "components/app/Placeholder.svelte"
|
||||
import ScreenPlaceholder from "components/app/ScreenPlaceholder.svelte"
|
||||
import ComponentPlaceholder from "components/app/ComponentPlaceholder.svelte"
|
||||
import { DNDPlaceholderID } from "constants"
|
||||
|
||||
export let instance = {}
|
||||
export let isLayout = false
|
||||
|
@ -458,7 +459,7 @@
|
|||
class:editing
|
||||
class:block={isBlock}
|
||||
class:explode={interactive && hasChildren && inDndPath}
|
||||
class:placeholder={id === "placeholder"}
|
||||
class:placeholder={id === DNDPlaceholderID}
|
||||
data-id={id}
|
||||
data-name={name}
|
||||
data-icon={icon}
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
import { get } from "svelte/store"
|
||||
import IndicatorSet from "./IndicatorSet.svelte"
|
||||
import { builderStore, componentStore } from "stores"
|
||||
import PlaceholderOverlay from "./PlaceholderOverlay.svelte"
|
||||
import DNDPlaceholderOverlay from "./DNDPlaceholderOverlay.svelte"
|
||||
import { Utils } from "@budibase/frontend-core"
|
||||
import { findComponentById } from "utils/components.js"
|
||||
|
||||
let sourceId
|
||||
let sourceInfo
|
||||
let targetInfo
|
||||
let dropInfo
|
||||
|
||||
|
@ -15,7 +15,8 @@
|
|||
// the value of one of the properties actually changes
|
||||
$: parent = dropInfo?.parent
|
||||
$: index = dropInfo?.index
|
||||
$: builderStore.actions.updateDNDPlaceholder(parent, index)
|
||||
$: bounds = sourceInfo?.bounds
|
||||
$: builderStore.actions.updateDNDPlaceholder(parent, index, bounds)
|
||||
|
||||
// Util to get the inner DOM node by a component ID
|
||||
const getDOMNode = id => {
|
||||
|
@ -37,14 +38,14 @@
|
|||
// Callback when drag stops (whether dropped or not)
|
||||
const stopDragging = () => {
|
||||
// Reset state
|
||||
sourceId = null
|
||||
sourceInfo = null
|
||||
targetInfo = null
|
||||
dropInfo = null
|
||||
builderStore.actions.setDragging(false)
|
||||
|
||||
// Reset listener
|
||||
if (sourceId) {
|
||||
const component = document.getElementsByClassName(sourceId)[0]
|
||||
if (sourceInfo) {
|
||||
const component = document.getElementsByClassName(sourceInfo.id)[0]
|
||||
if (component) {
|
||||
component.removeEventListener("dragend", stopDragging)
|
||||
}
|
||||
|
@ -65,14 +66,33 @@
|
|||
component.addEventListener("dragend", stopDragging)
|
||||
|
||||
// Update state
|
||||
sourceId = component.dataset.id
|
||||
builderStore.actions.selectComponent(sourceId)
|
||||
const parentId = component.dataset.parent
|
||||
const parent = findComponentById(
|
||||
get(componentStore).currentAsset.props,
|
||||
parentId
|
||||
)
|
||||
const index = parent._children.findIndex(
|
||||
x => x._id === component.dataset.id
|
||||
)
|
||||
sourceInfo = {
|
||||
id: component.dataset.id,
|
||||
bounds: component.children[0].getBoundingClientRect(),
|
||||
parent: parentId,
|
||||
index,
|
||||
}
|
||||
builderStore.actions.selectComponent(sourceInfo.id)
|
||||
builderStore.actions.setDragging(true)
|
||||
|
||||
// Execute this asynchronously so we don't kill the drag event by hiding
|
||||
// the component in the same handler as starting the drag event
|
||||
// Set initial drop info to show placeholder exactly where the dragged
|
||||
// component is.
|
||||
// Execute this asynchronously to prevent bugs caused by updating state in
|
||||
// the same handler as selecting a new component (which causes a client
|
||||
// re-initialisation).
|
||||
setTimeout(() => {
|
||||
onDragEnter(e)
|
||||
dropInfo = {
|
||||
parent: parentId,
|
||||
index,
|
||||
}
|
||||
}, 0)
|
||||
}
|
||||
|
||||
|
@ -182,7 +202,7 @@
|
|||
|
||||
// Callback when on top of a component
|
||||
const onDragOver = e => {
|
||||
if (!sourceId || !targetInfo) {
|
||||
if (!sourceInfo || !targetInfo) {
|
||||
return
|
||||
}
|
||||
handleEvent(e)
|
||||
|
@ -190,14 +210,14 @@
|
|||
|
||||
// Callback when entering a potential drop target
|
||||
const onDragEnter = e => {
|
||||
if (!sourceId) {
|
||||
if (!sourceInfo) {
|
||||
return
|
||||
}
|
||||
|
||||
// Find the next valid component to consider dropping over, ignoring nested
|
||||
// block components
|
||||
const component = e.target?.closest?.(
|
||||
`.component:not(.block):not(.${sourceId})`
|
||||
`.component:not(.block):not(.${sourceInfo.id})`
|
||||
)
|
||||
if (component && component.classList.contains("droppable")) {
|
||||
targetInfo = {
|
||||
|
@ -216,7 +236,7 @@
|
|||
let target, mode
|
||||
|
||||
// Convert parent + index into target + mode
|
||||
if (sourceId && dropInfo?.parent && dropInfo.index != null) {
|
||||
if (sourceInfo && dropInfo?.parent && dropInfo.index != null) {
|
||||
const parent = findComponentById(
|
||||
get(componentStore).currentAsset?.props,
|
||||
dropInfo.parent
|
||||
|
@ -225,9 +245,17 @@
|
|||
return
|
||||
}
|
||||
|
||||
// Do nothing if we didn't change the location
|
||||
if (
|
||||
sourceInfo.parent === dropInfo.parent &&
|
||||
sourceInfo.index === dropInfo.index
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
// Filter out source component and placeholder from consideration
|
||||
const children = parent._children?.filter(
|
||||
x => x._id !== "placeholder" && x._id !== sourceId
|
||||
x => x._id !== "placeholder" && x._id !== sourceInfo.id
|
||||
)
|
||||
|
||||
// Use inside if no existing children
|
||||
|
@ -244,7 +272,7 @@
|
|||
}
|
||||
|
||||
if (target && mode) {
|
||||
builderStore.actions.moveComponent(sourceId, target, mode)
|
||||
builderStore.actions.moveComponent(sourceInfo.id, target, mode)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -278,5 +306,5 @@
|
|||
/>
|
||||
|
||||
{#if $builderStore.isDragging}
|
||||
<PlaceholderOverlay />
|
||||
<DNDPlaceholderOverlay />
|
||||
{/if}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
<script>
|
||||
import { builderStore } from "stores"
|
||||
import { DNDPlaceholderID } from "constants"
|
||||
|
||||
$: style = getStyle($builderStore.dndBounds)
|
||||
|
||||
const getStyle = bounds => {
|
||||
if (!bounds) {
|
||||
return null
|
||||
}
|
||||
return `--height: ${bounds.height}px; --width: ${bounds.width}px;`
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if style}
|
||||
<div class="wrapper">
|
||||
<div class="placeholder" id={DNDPlaceholderID} {style} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.wrapper {
|
||||
overflow: hidden;
|
||||
}
|
||||
.placeholder {
|
||||
display: block;
|
||||
height: var(--height);
|
||||
width: var(--width);
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
|
@ -1,11 +1,12 @@
|
|||
<script>
|
||||
import { onMount } from "svelte"
|
||||
import { DNDPlaceholderID } from "constants"
|
||||
|
||||
let left, top, height, width
|
||||
|
||||
onMount(() => {
|
||||
const interval = setInterval(() => {
|
||||
const node = document.getElementById("placeholder")
|
||||
const node = document.getElementById(DNDPlaceholderID)
|
||||
if (!node) {
|
||||
height = 0
|
||||
width = 0
|
|
@ -1,11 +0,0 @@
|
|||
<div id="placeholder" class="placeholder" />
|
||||
|
||||
<style>
|
||||
.placeholder {
|
||||
display: block;
|
||||
min-height: 64px;
|
||||
min-width: 64px;
|
||||
flex: 0 0 64px;
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
|
@ -29,3 +29,7 @@ export const ActionTypes = {
|
|||
ClearForm: "ClearForm",
|
||||
ChangeFormStep: "ChangeFormStep",
|
||||
}
|
||||
|
||||
export const DNDPlaceholderID = "dnd-placeholder"
|
||||
export const DNDPlaceholderType = "dnd-placeholder"
|
||||
export const ScreenslotType = "screenslot"
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
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 })
|
||||
|
@ -21,9 +20,9 @@ const createBuilderStore = () => {
|
|||
navigation: null,
|
||||
hiddenComponentIds: [],
|
||||
usedPlugins: null,
|
||||
|
||||
dndParent: null,
|
||||
dndIndex: null,
|
||||
dndBounds: null,
|
||||
|
||||
// Legacy - allow the builder to specify a layout
|
||||
layout: null,
|
||||
|
@ -109,10 +108,11 @@ const createBuilderStore = () => {
|
|||
// Notify the builder so we can reload component definitions
|
||||
dispatchEvent("reload-plugin")
|
||||
},
|
||||
updateDNDPlaceholder: (parent, index) => {
|
||||
updateDNDPlaceholder: (parent, index, bounds) => {
|
||||
store.update(state => {
|
||||
state.dndParent = parent
|
||||
state.dndIndex = index
|
||||
state.dndBounds = bounds
|
||||
return state
|
||||
})
|
||||
},
|
||||
|
|
|
@ -5,8 +5,9 @@ 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 DNDPlaceholder from "../components/preview/DNDPlaceholder.svelte"
|
||||
import * as AppComponents from "../components/app/index.js"
|
||||
import { DNDPlaceholderType, ScreenslotType } from "../constants.js"
|
||||
|
||||
const budibasePrefix = "@budibase/standard-components/"
|
||||
|
||||
|
@ -109,9 +110,9 @@ const createComponentStore = () => {
|
|||
}
|
||||
|
||||
// Screenslot is an edge case
|
||||
if (type === "screenslot") {
|
||||
if (type === ScreenslotType) {
|
||||
type = `${budibasePrefix}${type}`
|
||||
} else if (type === "placeholder") {
|
||||
} else if (type === DNDPlaceholderType) {
|
||||
return {}
|
||||
}
|
||||
|
||||
|
@ -130,10 +131,10 @@ const createComponentStore = () => {
|
|||
if (!type) {
|
||||
return null
|
||||
}
|
||||
if (type === "screenslot") {
|
||||
if (type === ScreenslotType) {
|
||||
return Router
|
||||
} else if (type === "placeholder") {
|
||||
return Placeholder
|
||||
} else if (type === DNDPlaceholderType) {
|
||||
return DNDPlaceholder
|
||||
}
|
||||
|
||||
// Handle budibase components
|
||||
|
|
|
@ -5,6 +5,7 @@ import { appStore } from "./app"
|
|||
import { RoleUtils } from "@budibase/frontend-core"
|
||||
import { findComponentById, findComponentParent } from "../utils/components.js"
|
||||
import { Helpers } from "@budibase/bbui"
|
||||
import { DNDPlaceholderID, DNDPlaceholderType } from "constants"
|
||||
|
||||
const createScreenStore = () => {
|
||||
const store = derived(
|
||||
|
@ -58,8 +59,8 @@ const createScreenStore = () => {
|
|||
|
||||
// Insert placeholder
|
||||
const placeholder = {
|
||||
_component: "placeholder",
|
||||
_id: "placeholder",
|
||||
_component: DNDPlaceholderID,
|
||||
_id: DNDPlaceholderType,
|
||||
static: true,
|
||||
}
|
||||
let parent = findComponentById(activeScreen.props, dndParent)
|
||||
|
|
Loading…
Reference in New Issue