Add working DND placeholder support for grid screens
This commit is contained in:
parent
73d3ce2038
commit
5b5a0d2ba8
|
@ -223,11 +223,13 @@
|
|||
})
|
||||
|
||||
const onDragStart = (e, component) => {
|
||||
console.log("DRAG START")
|
||||
e.dataTransfer.setDragImage(ghost, 0, 0)
|
||||
previewStore.startDrag(component)
|
||||
}
|
||||
|
||||
const onDragEnd = () => {
|
||||
console.log("DRAG END")
|
||||
previewStore.stopDrag()
|
||||
}
|
||||
</script>
|
||||
|
@ -314,7 +316,6 @@
|
|||
}
|
||||
.component:hover {
|
||||
background: var(--spectrum-global-color-gray-300);
|
||||
cursor: grab;
|
||||
}
|
||||
.component :global(.spectrum-Body) {
|
||||
line-height: 1.2 !important;
|
||||
|
|
|
@ -199,8 +199,8 @@
|
|||
} else if (type === "reload-plugin") {
|
||||
await componentStore.refreshDefinitions()
|
||||
} else if (type === "drop-new-component") {
|
||||
const { component, parent, index } = data
|
||||
await componentStore.create(component, null, parent, index)
|
||||
const { component, parent, index, props } = data
|
||||
await componentStore.create(component, props, parent, index)
|
||||
} else if (type === "add-parent-component") {
|
||||
const { componentId, parentType } = data
|
||||
await componentStore.addParent(componentId, parentType)
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import { get } from "svelte/store"
|
||||
import { BudiStore } from "../BudiStore"
|
||||
import { componentStore } from "./components"
|
||||
import { selectedScreen } from "./screens"
|
||||
|
||||
type PreviewDevice = "desktop" | "tablet" | "mobile"
|
||||
type PreviewEventHandler = (name: string, payload?: any) => void
|
||||
|
@ -56,21 +54,10 @@ export class PreviewStore extends BudiStore<PreviewState> {
|
|||
}))
|
||||
}
|
||||
|
||||
async startDrag(componentType: string) {
|
||||
let componentId
|
||||
const gridScreen = get(selectedScreen)?.props?.layout === "grid"
|
||||
if (gridScreen) {
|
||||
const component = await componentStore.create(componentType, {
|
||||
_placeholder: true,
|
||||
_styles: { normal: { opacity: 0 } },
|
||||
})
|
||||
componentId = component?._id
|
||||
}
|
||||
async startDrag(component: string) {
|
||||
this.sendEvent("dragging-new-component", {
|
||||
dragging: true,
|
||||
componentType,
|
||||
componentId,
|
||||
gridScreen,
|
||||
component,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -8,12 +8,14 @@
|
|||
dndStore,
|
||||
dndParent,
|
||||
dndIsDragging,
|
||||
isGridScreen,
|
||||
dndInitialised,
|
||||
} from "stores"
|
||||
import DNDPlaceholderOverlay from "./DNDPlaceholderOverlay.svelte"
|
||||
import { Utils } from "@budibase/frontend-core"
|
||||
import { findComponentById } from "utils/components.js"
|
||||
import { DNDPlaceholderID } from "constants"
|
||||
import { isGridEvent } from "utils/grid"
|
||||
import { findComponentById } from "@/utils/components.js"
|
||||
import { isGridEvent } from "@/utils/grid"
|
||||
import { DNDPlaceholderID } from "@/constants"
|
||||
|
||||
const ThrottleRate = 130
|
||||
|
||||
|
@ -219,9 +221,9 @@
|
|||
processEvent(e.clientX, e.clientY)
|
||||
}
|
||||
|
||||
// Callback when on top of a component.
|
||||
// Callback when on top of a component
|
||||
const onDragOver = e => {
|
||||
if (!source || !target) {
|
||||
if (!source || !target || $isGridScreen) {
|
||||
return
|
||||
}
|
||||
handleEvent(e)
|
||||
|
@ -233,6 +235,14 @@
|
|||
return
|
||||
}
|
||||
|
||||
// Mark as initialised if this is our first valid drag enter event
|
||||
if (!$dndInitialised) {
|
||||
dndStore.actions.markInitialised()
|
||||
}
|
||||
if ($isGridScreen) {
|
||||
return
|
||||
}
|
||||
|
||||
// Find the next valid component to consider dropping over, ignoring nested
|
||||
// block components
|
||||
const component = e.target?.closest?.(
|
||||
|
@ -262,7 +272,8 @@
|
|||
builderStore.actions.dropNewComponent(
|
||||
source.newComponentType,
|
||||
drop.parent,
|
||||
drop.index
|
||||
drop.index,
|
||||
$dndStore.meta.newComponentProps
|
||||
)
|
||||
dropping = false
|
||||
stopDragging()
|
||||
|
|
|
@ -1,38 +1,56 @@
|
|||
<script>
|
||||
import { onMount } from "svelte"
|
||||
import { DNDPlaceholderID } from "constants"
|
||||
import { Utils } from "@budibase/frontend-core"
|
||||
import { dndInitialised } from "@/stores"
|
||||
import { DNDPlaceholderID } from "@/constants"
|
||||
|
||||
let left, top, height, width
|
||||
let observing = false
|
||||
|
||||
// Observe style changes in the placeholder DOM node and use this to trigger
|
||||
// a redraw of our overlay (grid screens)
|
||||
const observer = new MutationObserver(mutations => {
|
||||
if (mutations.some(mutation => mutation.attributeName === "style")) {
|
||||
debouncedUpdate()
|
||||
}
|
||||
})
|
||||
|
||||
const updatePosition = () => {
|
||||
let node = document.getElementsByClassName(DNDPlaceholderID)[0]
|
||||
const insideGrid = node?.dataset.insideGrid === "true"
|
||||
const wrapperNode = document.getElementsByClassName(DNDPlaceholderID)[0]
|
||||
let domNode = wrapperNode
|
||||
const insideGrid = wrapperNode?.dataset.insideGrid === "true"
|
||||
if (!insideGrid) {
|
||||
node = document.getElementsByClassName(`${DNDPlaceholderID}-dom`)[0]
|
||||
domNode = document.getElementsByClassName(`${DNDPlaceholderID}-dom`)[0]
|
||||
}
|
||||
if (!node) {
|
||||
if (!domNode) {
|
||||
height = 0
|
||||
width = 0
|
||||
} else {
|
||||
const bounds = node.getBoundingClientRect()
|
||||
const bounds = domNode.getBoundingClientRect()
|
||||
left = bounds.left
|
||||
top = bounds.top
|
||||
height = bounds.height
|
||||
width = bounds.width
|
||||
}
|
||||
|
||||
// Initialise observer if not already done
|
||||
if (!observing && wrapperNode) {
|
||||
observing = true
|
||||
observer.observe(wrapperNode, { attributes: true })
|
||||
}
|
||||
}
|
||||
const debouncedUpdate = Utils.domDebounce(updatePosition)
|
||||
|
||||
onMount(() => {
|
||||
const interval = setInterval(debouncedUpdate, 100)
|
||||
return () => {
|
||||
observer.disconnect()
|
||||
clearInterval(interval)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if left != null && top != null && width && height}
|
||||
{#if left != null && top != null && width && height && $dndInitialised}
|
||||
<div
|
||||
class="overlay"
|
||||
style="left: {left}px; top: {top}px; width: {width}px; height: {height}px;"
|
||||
|
@ -45,7 +63,6 @@
|
|||
z-index: 800;
|
||||
background: hsl(160, 64%, 90%);
|
||||
border-radius: 4px;
|
||||
transition: all 130ms ease-out;
|
||||
border: 2px solid var(--spectrum-global-color-static-green-500);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,15 +1,22 @@
|
|||
<script>
|
||||
import { onMount, onDestroy, getContext } from "svelte"
|
||||
import { builderStore, componentStore } from "stores"
|
||||
import {
|
||||
builderStore,
|
||||
componentStore,
|
||||
dndIsDragging,
|
||||
dndIsNewComponent,
|
||||
dndStore,
|
||||
isGridScreen,
|
||||
} from "stores"
|
||||
import { Utils, memo } from "@budibase/frontend-core"
|
||||
import { GridRowHeight } from "constants"
|
||||
import { DNDPlaceholderID, GridRowHeight } from "@/constants"
|
||||
import {
|
||||
isGridEvent,
|
||||
GridParams,
|
||||
getGridVar,
|
||||
Devices,
|
||||
GridDragModes,
|
||||
} from "utils/grid"
|
||||
} from "@/utils/grid"
|
||||
|
||||
const context = getContext("context")
|
||||
|
||||
|
@ -37,6 +44,21 @@
|
|||
$: instance = componentStore.actions.getComponentInstance(id)
|
||||
$: $instance?.setEphemeralStyles($styles)
|
||||
|
||||
// Keep DND store up to date with grid styles if dragging a new component
|
||||
// on to a grid screen
|
||||
$: {
|
||||
if ($dndIsNewComponent) {
|
||||
dndStore.actions.updateNewComponentProps({
|
||||
_styles: {
|
||||
normal: $styles,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Reset when not dragging new components
|
||||
$: !$dndIsDragging && stopDragging()
|
||||
|
||||
// Sugar for a combination of both min and max
|
||||
const minMax = (value, min, max) => Math.min(max, Math.max(min, value))
|
||||
|
||||
|
@ -98,6 +120,47 @@
|
|||
processEvent(e.clientX, e.clientY)
|
||||
}
|
||||
|
||||
const startDraggingPlaceholder = () => {
|
||||
console.log("START PLACEHOLDER")
|
||||
const mode = GridDragModes.Move
|
||||
const id = DNDPlaceholderID
|
||||
|
||||
// Find grid parent and read from DOM
|
||||
const domComponent = document.getElementsByClassName(id)[0]
|
||||
const domGrid = domComponent?.closest(".grid")
|
||||
if (!domGrid) {
|
||||
return
|
||||
}
|
||||
const styles = getComputedStyle(domComponent)
|
||||
const bounds = domComponent.getBoundingClientRect()
|
||||
|
||||
// Show as active
|
||||
domComponent.classList.add("dragging")
|
||||
domGrid.classList.add("highlight")
|
||||
builderStore.actions.selectComponent(id)
|
||||
|
||||
// Update state
|
||||
dragInfo = {
|
||||
domComponent,
|
||||
domGrid,
|
||||
id,
|
||||
gridId: domGrid.parentNode.dataset.id,
|
||||
mode,
|
||||
grid: {
|
||||
startX: bounds.left + bounds.width / 2,
|
||||
startY: bounds.top + bounds.height / 2,
|
||||
rowStart: parseInt(styles["grid-row-start"]),
|
||||
rowEnd: parseInt(styles["grid-row-end"]),
|
||||
colStart: parseInt(styles["grid-column-start"]),
|
||||
colEnd: parseInt(styles["grid-column-end"]),
|
||||
},
|
||||
}
|
||||
|
||||
// Add event handler to clear all drag state when dragging ends
|
||||
console.log("add up listener")
|
||||
document.addEventListener("mouseup", stopDragging)
|
||||
}
|
||||
|
||||
// Callback when initially starting a drag on a draggable component
|
||||
const onDragStart = e => {
|
||||
if (!isGridEvent(e)) {
|
||||
|
@ -158,11 +221,15 @@
|
|||
}
|
||||
|
||||
// Add event handler to clear all drag state when dragging ends
|
||||
dragInfo.domTarget.addEventListener("dragend", stopDragging)
|
||||
dragInfo.domTarget.addEventListener("dragend", stopDragging, false)
|
||||
}
|
||||
|
||||
const onDragOver = e => {
|
||||
if (!dragInfo) {
|
||||
// Check if we're dragging a new component
|
||||
if ($dndIsDragging && $dndIsNewComponent && $isGridScreen) {
|
||||
startDraggingPlaceholder()
|
||||
}
|
||||
return
|
||||
}
|
||||
handleEvent(e)
|
||||
|
@ -178,7 +245,7 @@
|
|||
// Reset DOM
|
||||
domComponent.classList.remove("dragging")
|
||||
domGrid.classList.remove("highlight")
|
||||
domTarget.removeEventListener("dragend", stopDragging)
|
||||
domTarget?.removeEventListener("dragend", stopDragging)
|
||||
|
||||
// Save changes
|
||||
if ($styles) {
|
||||
|
@ -198,5 +265,6 @@
|
|||
onDestroy(() => {
|
||||
document.removeEventListener("dragstart", onDragStart, false)
|
||||
document.removeEventListener("dragover", onDragOver, false)
|
||||
document.removeEventListener("mouseup", stopDragging, false)
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -36,6 +36,8 @@ import * as internal from "svelte/internal"
|
|||
window.svelte_internal = internal
|
||||
window.svelte = svelte
|
||||
|
||||
console.log("NEW CLIENT")
|
||||
|
||||
// Initialise spectrum icons
|
||||
// eslint-disable-next-line local-rules/no-budibase-imports
|
||||
import loadSpectrumIcons from "@budibase/bbui/spectrum-icons-vite.js"
|
||||
|
@ -161,14 +163,13 @@ const loadBudibase = async () => {
|
|||
const block = blockStore.actions.getBlock(data)
|
||||
block?.eject()
|
||||
} else if (type === "dragging-new-component") {
|
||||
const { dragging, component, componentId } = data
|
||||
const { dragging, component } = data
|
||||
if (dragging) {
|
||||
const definition =
|
||||
componentStore.actions.getComponentDefinition(component)
|
||||
dndStore.actions.startDraggingNewComponent({
|
||||
component,
|
||||
definition,
|
||||
componentId,
|
||||
})
|
||||
} else {
|
||||
dndStore.actions.reset()
|
||||
|
|
|
@ -77,11 +77,12 @@ const createBuilderStore = () => {
|
|||
mode,
|
||||
})
|
||||
},
|
||||
dropNewComponent: (component, parent, index) => {
|
||||
dropNewComponent: (component, parent, index, props) => {
|
||||
eventStore.actions.dispatchEvent("drop-new-component", {
|
||||
component,
|
||||
parent,
|
||||
index,
|
||||
props,
|
||||
})
|
||||
},
|
||||
setEditMode: enabled => {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { writable } from "svelte/store"
|
||||
import { writable, get } from "svelte/store"
|
||||
import { derivedMemo } from "@budibase/frontend-core"
|
||||
import { screenStore } from "@/stores"
|
||||
import { ScreenslotID } from "@/constants"
|
||||
|
||||
const createDndStore = () => {
|
||||
const initialState = {
|
||||
|
@ -11,6 +13,12 @@ const createDndStore = () => {
|
|||
|
||||
// Info about where the component would be dropped
|
||||
drop: null,
|
||||
|
||||
// Metadata about the event
|
||||
meta: {
|
||||
initialised: false,
|
||||
newComponentProps: null,
|
||||
},
|
||||
}
|
||||
const store = writable(initialState)
|
||||
|
||||
|
@ -21,35 +29,53 @@ const createDndStore = () => {
|
|||
})
|
||||
}
|
||||
|
||||
const startDraggingNewComponent = ({
|
||||
component,
|
||||
definition,
|
||||
componentId,
|
||||
}) => {
|
||||
const startDraggingNewComponent = ({ component, definition }) => {
|
||||
console.log("start", component, definition)
|
||||
if (!component) {
|
||||
return
|
||||
}
|
||||
|
||||
let target, drop
|
||||
const screen = get(screenStore)?.activeScreen
|
||||
const isGridScreen = screen?.props?.layout === "grid"
|
||||
if (isGridScreen) {
|
||||
const id = screen?.props?._id
|
||||
drop = {
|
||||
parent: id,
|
||||
index: screen?.props?._children?.length,
|
||||
}
|
||||
target = {
|
||||
id,
|
||||
parent: ScreenslotID,
|
||||
node: null,
|
||||
empty: false,
|
||||
acceptsChildren: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Get size of new component so we can show a properly sized placeholder
|
||||
const width = definition?.size?.width || 128
|
||||
const height = definition?.size?.height || 64
|
||||
|
||||
store.set({
|
||||
...initialState,
|
||||
isGridScreen,
|
||||
source: {
|
||||
id: null,
|
||||
parent: null,
|
||||
bounds: { height, width },
|
||||
index: null,
|
||||
newComponentType: component,
|
||||
newComponentId: componentId,
|
||||
},
|
||||
target,
|
||||
drop,
|
||||
})
|
||||
}
|
||||
|
||||
const updateTarget = ({ id, parent, node, empty, acceptsChildren }) => {
|
||||
store.update(state => {
|
||||
state.target = { id, parent, node, empty, acceptsChildren }
|
||||
console.log("TARGET", state.target)
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
@ -57,6 +83,7 @@ const createDndStore = () => {
|
|||
const updateDrop = ({ parent, index }) => {
|
||||
store.update(state => {
|
||||
state.drop = { parent, index }
|
||||
console.log("DROP", state.drop)
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
@ -65,6 +92,26 @@ const createDndStore = () => {
|
|||
store.set(initialState)
|
||||
}
|
||||
|
||||
const markInitialised = () => {
|
||||
store.update(state => ({
|
||||
...state,
|
||||
meta: {
|
||||
...state.meta,
|
||||
initialised: true,
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
const updateNewComponentProps = newComponentProps => {
|
||||
store.update(state => ({
|
||||
...state,
|
||||
meta: {
|
||||
...state.meta,
|
||||
newComponentProps,
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe: store.subscribe,
|
||||
actions: {
|
||||
|
@ -73,6 +120,8 @@ const createDndStore = () => {
|
|||
updateTarget,
|
||||
updateDrop,
|
||||
reset,
|
||||
markInitialised,
|
||||
updateNewComponentProps,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -87,10 +136,7 @@ export const dndParent = derivedMemo(dndStore, x => x.drop?.parent)
|
|||
export const dndIndex = derivedMemo(dndStore, x => x.drop?.index)
|
||||
export const dndBounds = derivedMemo(dndStore, x => x.source?.bounds)
|
||||
export const dndIsDragging = derivedMemo(dndStore, x => !!x.source)
|
||||
export const dndNewComponentId = derivedMemo(
|
||||
dndStore,
|
||||
x => x.source?.newComponentId
|
||||
)
|
||||
export const dndInitialised = derivedMemo(dndStore, x => x.meta.initialised)
|
||||
export const dndIsNewComponent = derivedMemo(
|
||||
dndStore,
|
||||
x => x.source?.newComponentType != null
|
||||
|
|
|
@ -25,6 +25,7 @@ export {
|
|||
dndBounds,
|
||||
dndIsNewComponent,
|
||||
dndIsDragging,
|
||||
dndInitialised,
|
||||
} from "./dnd"
|
||||
export { sidePanelStore } from "./sidePanel"
|
||||
export { modalStore } from "./modal"
|
||||
|
|
|
@ -7,7 +7,7 @@ import { dndIndex, dndParent, dndIsNewComponent, dndBounds } from "./dnd.js"
|
|||
import { RoleUtils } from "@budibase/frontend-core"
|
||||
import { findComponentById, findComponentParent } from "../utils/components.js"
|
||||
import { Helpers } from "@budibase/bbui"
|
||||
import { DNDPlaceholderID, ScreenslotID, ScreenslotType } from "constants"
|
||||
import { DNDPlaceholderID, ScreenslotID, ScreenslotType } from "@/constants"
|
||||
|
||||
const createScreenStore = () => {
|
||||
const store = derived(
|
||||
|
@ -99,7 +99,6 @@ const createScreenStore = () => {
|
|||
normal: {
|
||||
width: `${$dndBounds?.width || 400}px`,
|
||||
height: `${$dndBounds?.height || 200}px`,
|
||||
opacity: 0,
|
||||
"--default-width": $dndBounds?.width || 400,
|
||||
"--default-height": $dndBounds?.height || 200,
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue