Add initial DND implementation with working functionality for dropping inside components

This commit is contained in:
Andrew Kingston 2021-09-16 07:28:59 +01:00
parent 6336409378
commit 4be4dd014d
6 changed files with 114 additions and 2 deletions

View File

@ -1,5 +1,6 @@
<script> <script>
import { onMount } from "svelte" import { onMount } from "svelte"
import { get } from "svelte/store"
import { store, currentAsset } from "builderStore" import { store, currentAsset } from "builderStore"
import iframeTemplate from "./iframeTemplate" import iframeTemplate from "./iframeTemplate"
import { Screen } from "builderStore/store/screenTemplates/utils/Screen" import { Screen } from "builderStore/store/screenTemplates/utils/Screen"
@ -7,6 +8,7 @@
import ConfirmDialog from "components/common/ConfirmDialog.svelte" import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import { ProgressCircle, Layout, Heading, Body } from "@budibase/bbui" import { ProgressCircle, Layout, Heading, Body } from "@budibase/bbui"
import ErrorSVG from "assets/error.svg?raw" import ErrorSVG from "assets/error.svg?raw"
import { findComponent } from "builderStore/storeUtils"
let iframe let iframe
let layout let layout
@ -111,6 +113,20 @@
// Wait for this event to show the client library if intelligent // Wait for this event to show the client library if intelligent
// loading is supported // loading is supported
loading = false loading = false
} else if (type === "move-component") {
// Copy
const sourceComponent = findComponent(
get(currentAsset).props,
data.componentId
)
const destinationComponent = findComponent(
get(currentAsset).props,
data.destinationComponentId
)
if (sourceComponent && destinationComponent) {
store.actions.components.copy(sourceComponent, true)
store.actions.components.paste(destinationComponent, data.mode)
}
} else { } else {
console.warning(`Client sent unknown event type: ${type}`) console.warning(`Client sent unknown event type: ${type}`)
} }

View File

@ -23,6 +23,7 @@
import SelectionIndicator from "components/preview/SelectionIndicator.svelte" import SelectionIndicator from "components/preview/SelectionIndicator.svelte"
import HoverIndicator from "components/preview/HoverIndicator.svelte" import HoverIndicator from "components/preview/HoverIndicator.svelte"
import CustomThemeWrapper from "./CustomThemeWrapper.svelte" import CustomThemeWrapper from "./CustomThemeWrapper.svelte"
import DNDHandler from "components/preview/DNDHandler.svelte"
import ErrorSVG from "../../../builder/assets/error.svg" import ErrorSVG from "../../../builder/assets/error.svg"
// Provide contexts // Provide contexts
@ -122,6 +123,7 @@
{#if $builderStore.inBuilder} {#if $builderStore.inBuilder}
<SelectionIndicator /> <SelectionIndicator />
<HoverIndicator /> <HoverIndicator />
<DNDHandler />
{/if} {/if}
</div> </div>
</StateBindingsProvider> </StateBindingsProvider>

View File

@ -178,6 +178,7 @@
data-type={interactive ? "component" : ""} data-type={interactive ? "component" : ""}
data-id={id} data-id={id}
data-name={name} data-name={name}
data-droppable={definition?.hasChildren || false}
> >
<svelte:component this={constructor} {...componentSettings}> <svelte:component this={constructor} {...componentSettings}>
{#if children.length} {#if children.length}
@ -196,4 +197,7 @@
.component { .component {
display: contents; display: contents;
} }
.component > :global(*:hover) {
cursor: pointer;
}
</style> </style>

View File

@ -0,0 +1,83 @@
<script>
import { onMount } from "svelte"
import IndicatorSet from "./IndicatorSet.svelte"
import { builderStore } from "stores"
let dragTarget
let dropTarget
// Callback when initially starting a drag on a draggable component
const onDragStart = e => {
dragTarget = e.target.dataset.componentId
e.target.style.opacity = 0.5
}
// Callback when drag stops (whether dropped or not)
const onDragEnd = e => {
// reset the transparency
dragTarget = null
e.target.style.opacity = ""
dropTarget = null
}
// Callback when on top of a component
const onDragOver = e => {
e.preventDefault()
}
// Callback when entering a potential drop target
const onDragEnter = e => {
const element = e.target.closest("[data-type='component']")
if (element && element.dataset.droppable === "true") {
dropTarget = element.dataset.id
} else {
dropTarget = null
}
}
// Callback when leaving a potential drop target.
// Since we don't style our targets, we don't need to unset anything.
const onDragLeave = () => {}
// Callback when dropping a drag on top of some component
const onDrop = e => {
e.preventDefault()
// Check if the target is droppable
const element = e.target.closest("[data-type='component']")
if (element && element.dataset.droppable === "true") {
builderStore.actions.moveComponent(dragTarget, dropTarget, "inside")
}
}
onMount(() => {
// Events fired on the draggable target
document.addEventListener("dragstart", onDragStart, false)
document.addEventListener("dragend", onDragEnd, false)
// Events fired on the drop targets
document.addEventListener("dragover", onDragOver, false)
document.addEventListener("dragenter", onDragEnter, false)
document.addEventListener("dragleave", onDragLeave, false)
document.addEventListener("drop", onDrop, false)
return () => {
// Events fired on the draggable target
document.removeEventListener("dragstart", onDragStart, false)
document.removeEventListener("dragend", onDragEnd, false)
// Events fired on the drop targets
document.removeEventListener("dragover", onDragOver, false)
document.removeEventListener("dragenter", onDragEnter, false)
document.removeEventListener("dragleave", onDragLeave, false)
document.removeEventListener("drop", onDrop, false)
}
})
</script>
<IndicatorSet
componentId={dropTarget}
color="var(--spectrum-global-color-static-red-600)"
zIndex="930"
transition
/>

View File

@ -64,13 +64,18 @@ const createBuilderStore = () => {
dispatchEvent("preview-loaded") dispatchEvent("preview-loaded")
}, },
setSelectedPath: path => { setSelectedPath: path => {
console.log("set to ")
console.log(path)
writableStore.update(state => { writableStore.update(state => {
state.selectedPath = path state.selectedPath = path
return state return state
}) })
}, },
moveComponent: (componentId, destinationComponentId, mode) => {
dispatchEvent("move-component", {
componentId,
destinationComponentId,
mode,
})
},
} }
return { return {
...writableStore, ...writableStore,

View File

@ -23,6 +23,8 @@ export const styleable = (node, styles = {}) => {
let applyHoverStyles let applyHoverStyles
let selectComponent let selectComponent
node.setAttribute("draggable", true)
// Creates event listeners and applies initial styles // Creates event listeners and applies initial styles
const setupStyles = (newStyles = {}) => { const setupStyles = (newStyles = {}) => {
// Use empty state styles as base styles if required, but let them, get // Use empty state styles as base styles if required, but let them, get