From 8cf3971b471a4c67a47de30f58702859f8148367 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 20 Sep 2021 08:26:44 +0100 Subject: [PATCH] Only allow dropping inside empty components that accept children to massively reduce the amount of unwanted drop targets due to parent container components --- .../client/src/components/Component.svelte | 15 ++++-- .../src/components/preview/DNDHandler.svelte | 53 ++++++++++--------- .../preview/DNDPositionIndicator.svelte | 10 ++-- 3 files changed, 41 insertions(+), 37 deletions(-) diff --git a/packages/client/src/components/Component.svelte b/packages/client/src/components/Component.svelte index 980e7cd80b..6192004b42 100644 --- a/packages/client/src/components/Component.svelte +++ b/packages/client/src/components/Component.svelte @@ -171,18 +171,23 @@ conditionalSettings = result.settingUpdates visible = nextVisible } + + // Drag and drop helper tags + $: draggable = interactive && !isLayout && !isScreen + $: droppable = interactive + $: dropInside = interactive && definition?.hasChildren && !children.length {#key propsHash} {#if constructor && componentSettings && (visible || inSelectedPath)}
{#if children.length} @@ -202,6 +207,6 @@ display: contents; } [data-draggable="true"] :global(*:hover) { - cursor: grab !important; + cursor: grab; } diff --git a/packages/client/src/components/preview/DNDHandler.svelte b/packages/client/src/components/preview/DNDHandler.svelte index fe4d7f4d59..2437386929 100644 --- a/packages/client/src/components/preview/DNDHandler.svelte +++ b/packages/client/src/components/preview/DNDHandler.svelte @@ -5,9 +5,7 @@ import DNDPositionIndicator from "./DNDPositionIndicator.svelte" import { builderStore } from "stores" - let dragTarget - let dropTarget - let dropMode + let dragInfo let dropInfo const getDOMNodeForComponent = component => { @@ -18,26 +16,28 @@ // Callback when initially starting a drag on a draggable component const onDragStart = e => { - if (!e.target?.dataset?.componentId) { + const parent = e.target.closest("[data-type='component']") + const child = getDOMNodeForComponent(e.target) + if (!parent?.dataset?.id || !child) { return } // Update state - dragTarget = e.target.dataset.componentId - builderStore.actions.selectComponent(dragTarget) + dragInfo = { + target: parent.dataset.id, + parent: parent.dataset.parent, + } + builderStore.actions.selectComponent(dragInfo.target) builderStore.actions.setDragging(true) // Highlight being dragged by setting opacity - const child = getDOMNodeForComponent(e.target) - if (child) { - child.style.opacity = "0.5" - } + child.style.opacity = "0.5" } // Callback when drag stops (whether dropped or not) const onDragEnd = e => { // Reset opacity style - if (dragTarget) { + if (dragInfo) { const child = getDOMNodeForComponent(e.target) if (child) { child.style.opacity = "" @@ -45,8 +45,7 @@ } // Reset state and styles - dragTarget = null - dropTarget = null + dragInfo = null dropInfo = null builderStore.actions.setDragging(false) } @@ -54,7 +53,7 @@ // Callback when on top of a component const onDragOver = e => { // Skip if we aren't validly dragging currently - if (!dragTarget || !dropInfo) { + if (!dragInfo || !dropInfo) { return } @@ -77,7 +76,7 @@ // If not available to drop inside, just check whether we are closer // to the top or bottom if (!droppableInside) { - dropMode = nearestEdge + dropInfo.mode = nearestEdge } // Otherwise determine whether the user wants to drop inside or at @@ -89,9 +88,9 @@ Math.round(top + height - edgeLimit), ] if (mouseY >= insideLimit[0] && mouseY <= insideLimit[1]) { - dropMode = "inside" + dropInfo.mode = "inside" } else { - dropMode = nearestEdge + dropInfo.mode = nearestEdge } } } @@ -99,7 +98,7 @@ // Callback when entering a potential drop target const onDragEnter = e => { // Skip if we aren't validly dragging currently - if (!dragTarget) { + if (!dragInfo) { return } @@ -107,7 +106,7 @@ if ( element && element.dataset.droppable && - element.dataset.id !== dragTarget + element.dataset.id !== dragInfo.target ) { // Ensure the dragging flag is always set. // There's a bit of a race condition between the app reinitialisation @@ -117,20 +116,20 @@ } // Store target ID - dropTarget = element.dataset.id + const target = element.dataset.id // Precompute and store some info to avoid recalculating everything in // dragOver const child = getDOMNodeForComponent(e.target) const bounds = child.getBoundingClientRect() dropInfo = { + target, name: element.dataset.name, droppableInside: element.dataset.droppableInside === "true", bounds, } } else { dropInfo = null - dropTarget = null } } @@ -141,8 +140,12 @@ // Callback when dropping a drag on top of some component const onDrop = e => { e.preventDefault() - if (dropTarget && dropMode) { - builderStore.actions.moveComponent(dragTarget, dropTarget, dropMode) + if (dropInfo) { + builderStore.actions.moveComponent( + dragInfo.target, + dropInfo.target, + dropInfo.mode + ) } } @@ -172,7 +175,7 @@ import Indicator from "./Indicator.svelte" - export let componentId export let dropInfo - export let mode export let zIndex export let color export let transition - $: dimensions = getDimensions(dropInfo?.bounds, mode) - $: prefix = mode === "above" ? "Above" : "Below" + $: dimensions = getDimensions(dropInfo?.bounds, dropInfo?.mode) + $: prefix = dropInfo?.mode === "above" ? "Above" : "Below" $: text = `${prefix} ${dropInfo?.name}` const getDimensions = (bounds, mode) => { @@ -25,8 +23,8 @@ } -{#key `${componentId}-${mode}`} - {#if dimensions && mode !== "inside"} +{#key `${dropInfo?.target}-${dropInfo?.mode}`} + {#if dimensions && dropInfo?.mode !== "inside"}