Add grid functionality separately to DND
This commit is contained in:
parent
774566d03b
commit
15bbc78847
|
@ -21,7 +21,6 @@
|
||||||
devToolsStore,
|
devToolsStore,
|
||||||
componentStore,
|
componentStore,
|
||||||
appStore,
|
appStore,
|
||||||
dndIsDragging,
|
|
||||||
dndComponentPath,
|
dndComponentPath,
|
||||||
} from "stores"
|
} from "stores"
|
||||||
import { Helpers } from "@budibase/bbui"
|
import { Helpers } from "@budibase/bbui"
|
||||||
|
@ -163,7 +162,7 @@
|
||||||
// nested layers. Only reset this when dragging stops.
|
// nested layers. Only reset this when dragging stops.
|
||||||
let pad = false
|
let pad = false
|
||||||
$: pad = pad || (interactive && hasChildren && inDndPath)
|
$: pad = pad || (interactive && hasChildren && inDndPath)
|
||||||
$: $dndIsDragging, (pad = false)
|
$: $builderStore.dragging, (pad = false)
|
||||||
|
|
||||||
// Update component context
|
// Update component context
|
||||||
$: store.set({
|
$: store.set({
|
||||||
|
@ -427,7 +426,7 @@
|
||||||
const scrollIntoView = () => {
|
const scrollIntoView = () => {
|
||||||
// Don't scroll into view if we selected this component because we were
|
// Don't scroll into view if we selected this component because we were
|
||||||
// starting dragging on it
|
// starting dragging on it
|
||||||
if (get(dndIsDragging)) {
|
if (get(builderStore).dragging) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const node = document.getElementsByClassName(id)?.[0]?.children[0]
|
const node = document.getElementsByClassName(id)?.[0]?.children[0]
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import { fade } from "svelte/transition"
|
import GridDNDHandler from "../preview/GridDNDHandler.svelte"
|
||||||
|
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
const { styleable, builderStore } = getContext("sdk")
|
const { styleable, builderStore } = getContext("sdk")
|
||||||
|
|
||||||
$: coords = generateCoords(12)
|
const cols = 12
|
||||||
|
|
||||||
|
let node
|
||||||
|
$: coords = generateCoords(cols)
|
||||||
|
|
||||||
const generateCoords = num => {
|
const generateCoords = num => {
|
||||||
let grid = []
|
let grid = []
|
||||||
|
@ -18,44 +21,36 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="grid" use:styleable={$component.styles}>
|
<div
|
||||||
|
bind:this={node}
|
||||||
|
class="grid"
|
||||||
|
use:styleable={$component.styles}
|
||||||
|
data-cols={cols}
|
||||||
|
>
|
||||||
<div class="underlay">
|
<div class="underlay">
|
||||||
{#each coords as coord}
|
{#each coords as coord}
|
||||||
<div class="placeholder" />
|
<div class="placeholder" />
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
<slot />
|
<slot />
|
||||||
{#if $builderStore.isDragging}
|
|
||||||
<div
|
|
||||||
class="overlay"
|
|
||||||
in:fade={{ duration: 130 }}
|
|
||||||
out:fade|self={{ duration: 130 }}
|
|
||||||
>
|
|
||||||
{#each coords as coord}
|
|
||||||
<div
|
|
||||||
class="placeholder grid-coord"
|
|
||||||
data-row={coord.row}
|
|
||||||
data-col={coord.col}
|
|
||||||
/>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if $builderStore.inBuilder && node}
|
||||||
|
<GridDNDHandler {node} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.grid {
|
.grid {
|
||||||
position: relative;
|
position: relative;
|
||||||
min-height: 400px;
|
min-height: 400px;
|
||||||
}
|
}
|
||||||
.grid,
|
.grid,
|
||||||
.underlay,
|
.underlay {
|
||||||
.overlay {
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(12, 1fr);
|
grid-template-columns: repeat(12, 1fr);
|
||||||
grid-template-rows: repeat(12, 1fr);
|
grid-template-rows: repeat(12, 1fr);
|
||||||
}
|
}
|
||||||
.underlay,
|
.underlay {
|
||||||
.overlay {
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
@ -68,12 +63,6 @@
|
||||||
.underlay {
|
.underlay {
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
.overlay {
|
|
||||||
z-index: 999;
|
|
||||||
background-color: var(--spectrum-global-color-gray-500);
|
|
||||||
border-color: var(--spectrum-global-color-gray-500);
|
|
||||||
opacity: 0.3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder {
|
.placeholder {
|
||||||
background-color: var(--spectrum-global-color-gray-100);
|
background-color: var(--spectrum-global-color-gray-100);
|
||||||
|
|
|
@ -2,13 +2,7 @@
|
||||||
import { onMount, onDestroy } from "svelte"
|
import { onMount, onDestroy } from "svelte"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import IndicatorSet from "./IndicatorSet.svelte"
|
import IndicatorSet from "./IndicatorSet.svelte"
|
||||||
import {
|
import { builderStore, screenStore, dndStore, dndParent } from "stores"
|
||||||
builderStore,
|
|
||||||
screenStore,
|
|
||||||
dndStore,
|
|
||||||
dndParent,
|
|
||||||
dndIsDragging,
|
|
||||||
} from "stores"
|
|
||||||
import DNDPlaceholderOverlay from "./DNDPlaceholderOverlay.svelte"
|
import DNDPlaceholderOverlay from "./DNDPlaceholderOverlay.svelte"
|
||||||
import { Utils } from "@budibase/frontend-core"
|
import { Utils } from "@budibase/frontend-core"
|
||||||
import { findComponentById } from "utils/components.js"
|
import { findComponentById } from "utils/components.js"
|
||||||
|
@ -22,6 +16,10 @@
|
||||||
$: target = $dndStore.target
|
$: target = $dndStore.target
|
||||||
$: drop = $dndStore.drop
|
$: drop = $dndStore.drop
|
||||||
|
|
||||||
|
const insideGrid = e => {
|
||||||
|
return e.target?.closest?.(".grid") != null
|
||||||
|
}
|
||||||
|
|
||||||
// Util to get the inner DOM node by a component ID
|
// Util to get the inner DOM node by a component ID
|
||||||
const getDOMNode = id => {
|
const getDOMNode = id => {
|
||||||
const component = document.getElementsByClassName(id)[0]
|
const component = document.getElementsByClassName(id)[0]
|
||||||
|
@ -51,10 +49,14 @@
|
||||||
|
|
||||||
// Reset state
|
// Reset state
|
||||||
dndStore.actions.reset()
|
dndStore.actions.reset()
|
||||||
|
builderStore.actions.setDragging(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback when initially starting a drag on a draggable component
|
// Callback when initially starting a drag on a draggable component
|
||||||
const onDragStart = e => {
|
const onDragStart = e => {
|
||||||
|
if (insideGrid(e)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const component = e.target.closest(".component")
|
const component = e.target.closest(".component")
|
||||||
if (!component?.classList.contains("draggable")) {
|
if (!component?.classList.contains("draggable")) {
|
||||||
return
|
return
|
||||||
|
@ -83,6 +85,7 @@
|
||||||
index,
|
index,
|
||||||
})
|
})
|
||||||
builderStore.actions.selectComponent(id)
|
builderStore.actions.selectComponent(id)
|
||||||
|
builderStore.actions.setDragging(true)
|
||||||
|
|
||||||
// Set initial drop info to show placeholder exactly where the dragged
|
// Set initial drop info to show placeholder exactly where the dragged
|
||||||
// component is.
|
// component is.
|
||||||
|
@ -99,9 +102,9 @@
|
||||||
|
|
||||||
// Core logic for handling drop events and determining where to render the
|
// Core logic for handling drop events and determining where to render the
|
||||||
// drop target placeholder
|
// drop target placeholder
|
||||||
const processEvent = (mouseX, mouseY) => {
|
const processEvent = Utils.throttle((mouseX, mouseY) => {
|
||||||
if (!target) {
|
if (!target) {
|
||||||
return null
|
return
|
||||||
}
|
}
|
||||||
let { id, parent, node, acceptsChildren, empty } = target
|
let { id, parent, node, acceptsChildren, empty } = target
|
||||||
|
|
||||||
|
@ -201,17 +204,17 @@
|
||||||
parent: id,
|
parent: id,
|
||||||
index: idx,
|
index: idx,
|
||||||
})
|
})
|
||||||
}
|
}, ThrottleRate)
|
||||||
const throttledProcessEvent = Utils.throttle(processEvent, ThrottleRate)
|
|
||||||
|
|
||||||
const handleEvent = e => {
|
const handleEvent = e => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
throttledProcessEvent(e.clientX, e.clientY)
|
e.stopPropagation()
|
||||||
|
processEvent(e.clientX, e.clientY)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback when on top of a component
|
// Callback when on top of a component.
|
||||||
const onDragOver = e => {
|
const onDragOver = e => {
|
||||||
if (!source || !target) {
|
if (!source || !target || insideGrid(e)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
handleEvent(e)
|
handleEvent(e)
|
||||||
|
@ -219,7 +222,7 @@
|
||||||
|
|
||||||
// Callback when entering a potential drop target
|
// Callback when entering a potential drop target
|
||||||
const onDragEnter = e => {
|
const onDragEnter = e => {
|
||||||
if (!source) {
|
if (!source || insideGrid(e)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,8 +244,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback when dropping a drag on top of some component
|
// Callback when dropping a drag on top of some component
|
||||||
const onDrop = () => {
|
const onDrop = e => {
|
||||||
if (!source || !drop?.parent || drop?.index == null) {
|
if (!source || !drop?.parent || drop?.index == null || insideGrid(e)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -326,6 +329,6 @@
|
||||||
prefix="Inside"
|
prefix="Inside"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{#if $dndIsDragging}
|
{#if $builderStore.dragging}
|
||||||
<DNDPlaceholderOverlay />
|
<DNDPlaceholderOverlay />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -1,27 +1,17 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount, onDestroy } from "svelte"
|
import { onMount, onDestroy } from "svelte"
|
||||||
import { get } from "svelte/store"
|
import { builderStore, componentStore } from "stores"
|
||||||
import IndicatorSet from "./IndicatorSet.svelte"
|
|
||||||
import {
|
|
||||||
builderStore,
|
|
||||||
screenStore,
|
|
||||||
dndStore,
|
|
||||||
dndParent,
|
|
||||||
dndIsDragging,
|
|
||||||
} from "stores"
|
|
||||||
import DNDPlaceholderOverlay from "./DNDPlaceholderOverlay.svelte"
|
|
||||||
import { Utils } from "@budibase/frontend-core"
|
import { Utils } from "@budibase/frontend-core"
|
||||||
import { findComponentById } from "utils/components.js"
|
|
||||||
import { DNDPlaceholderID } from "constants"
|
|
||||||
import { DNDModes } from "stores/dnd.js"
|
|
||||||
|
|
||||||
const ThrottleRate = 130
|
let dragInfo
|
||||||
|
let gridStyles
|
||||||
|
|
||||||
// Cache some dnd store state as local variables as it massively helps
|
$: dragNode = getDOMNode(dragInfo?.id)
|
||||||
// performance. It lets us avoid calling svelte getters on every DOM action.
|
$: applyStyles(dragNode, gridStyles)
|
||||||
$: source = $dndStore.source
|
|
||||||
$: target = $dndStore.target
|
const insideGrid = e => {
|
||||||
$: drop = $dndStore.drop
|
return e.target?.closest?.(".grid") || e.target.classList.contains("anchor")
|
||||||
|
}
|
||||||
|
|
||||||
// Util to get the inner DOM node by a component ID
|
// Util to get the inner DOM node by a component ID
|
||||||
const getDOMNode = id => {
|
const getDOMNode = id => {
|
||||||
|
@ -29,535 +19,195 @@
|
||||||
return [...component.children][0]
|
return [...component.children][0]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Util to calculate the variance of a set of data
|
const applyStyles = (dragNode, gridStyles) => {
|
||||||
const variance = arr => {
|
if (!dragNode || !gridStyles) {
|
||||||
const mean = arr.reduce((a, b) => a + b, 0) / arr.length
|
return
|
||||||
let squareSum = 0
|
}
|
||||||
arr.forEach(value => {
|
Object.entries(gridStyles).forEach(([style, value]) => {
|
||||||
const delta = value - mean
|
dragNode.style[style] = value
|
||||||
squareSum += delta * delta
|
|
||||||
})
|
})
|
||||||
return squareSum / arr.length
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback when drag stops (whether dropped or not)
|
// Callback when drag stops (whether dropped or not)
|
||||||
const stopDragging = () => {
|
const stopDragging = () => {
|
||||||
|
// Save changes
|
||||||
|
if (gridStyles) {
|
||||||
|
builderStore.actions.updateStyles(gridStyles)
|
||||||
|
}
|
||||||
|
|
||||||
// Reset listener
|
// Reset listener
|
||||||
if (source?.id) {
|
if (dragInfo?.domTarget) {
|
||||||
const component = document.getElementsByClassName(source?.id)[0]
|
dragInfo.domTarget.removeEventListener("dragend", stopDragging)
|
||||||
if (component) {
|
|
||||||
component.removeEventListener("dragend", stopDragging)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset state
|
// Reset state
|
||||||
dndStore.actions.reset()
|
dragInfo = null
|
||||||
}
|
gridStyles = null
|
||||||
|
builderStore.actions.setDragging(false)
|
||||||
const calculatePointDelta = (point1, point2) => {
|
|
||||||
const deltaX = Math.abs(point1[0] - point2[0])
|
|
||||||
const deltaY = Math.abs(point1[1] - point2[1])
|
|
||||||
return Math.sqrt(deltaX * deltaX + deltaY * deltaY)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback when initially starting a drag on a draggable component
|
// Callback when initially starting a drag on a draggable component
|
||||||
const onDragStart = e => {
|
const onDragStart = e => {
|
||||||
// New stuff
|
if (!insideGrid(e)) {
|
||||||
// var img = new Image()
|
|
||||||
// img.src =
|
|
||||||
// "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="
|
|
||||||
// e.dataTransfer.setDragImage(img, 0, 0)
|
|
||||||
//
|
|
||||||
// // Resize component
|
|
||||||
// if (e.target.classList.contains("anchor")) {
|
|
||||||
// dragInfo = {
|
|
||||||
// target: e.target.dataset.id,
|
|
||||||
// side: e.target.dataset.side,
|
|
||||||
// mode: "resize",
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// // Drag component
|
|
||||||
// const parent = e.target.closest(".component")
|
|
||||||
// if (!parent?.classList.contains("draggable")) {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// dragInfo = {
|
|
||||||
// target: parent.dataset.id,
|
|
||||||
// parent: parent.dataset.parent,
|
|
||||||
// mode: "move",
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// if (!dragInfo) {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// builderStore.actions.selectComponent(dragInfo.target)
|
|
||||||
// builderStore.actions.setDragging(true)
|
|
||||||
|
|
||||||
// Current stuff
|
|
||||||
const component = e.target.closest(".component")
|
|
||||||
if (!component?.classList.contains("draggable")) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide drag ghost image
|
// Hide drag ghost image
|
||||||
e.dataTransfer.setDragImage(new Image(), 0, 0)
|
e.dataTransfer.setDragImage(new Image(), 0, 0)
|
||||||
|
|
||||||
// Add event handler to clear all drag state when dragging ends
|
// Extract state
|
||||||
component.addEventListener("dragend", stopDragging)
|
let mode, id, side
|
||||||
|
if (e.target.classList.contains("anchor")) {
|
||||||
//
|
// Handle resize
|
||||||
|
mode = "resize"
|
||||||
// Update state
|
id = e.target.dataset.id
|
||||||
const id = component.dataset.id
|
side = e.target.dataset.side
|
||||||
const parentId = component.dataset.parent
|
} else {
|
||||||
const parent = findComponentById(
|
// Handle move
|
||||||
get(screenStore).activeScreen?.props,
|
mode = "move"
|
||||||
parentId
|
const component = e.target.closest(".component")
|
||||||
)
|
id = component.dataset.id
|
||||||
const index = parent._children.findIndex(
|
|
||||||
x => x._id === component.dataset.id
|
|
||||||
)
|
|
||||||
dndStore.actions.startDraggingExistingComponent({
|
|
||||||
id,
|
|
||||||
bounds: component.children[0].getBoundingClientRect(),
|
|
||||||
parent: parentId,
|
|
||||||
index,
|
|
||||||
})
|
|
||||||
builderStore.actions.selectComponent(id)
|
|
||||||
|
|
||||||
// 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(() => {
|
|
||||||
dndStore.actions.updateDrop({
|
|
||||||
parent: parentId,
|
|
||||||
index,
|
|
||||||
})
|
|
||||||
}, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Core logic for handling drop events and determining where to render the
|
|
||||||
// drop target placeholder
|
|
||||||
const processEvent = (mouseX, mouseY) => {
|
|
||||||
if (!target) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
let { id, parent, node, acceptsChildren, empty } = target
|
|
||||||
|
|
||||||
// If we're over something that does not accept children then we go up a
|
|
||||||
// level and consider the mouse position relative to the parent
|
|
||||||
if (!acceptsChildren) {
|
|
||||||
id = parent
|
|
||||||
empty = false
|
|
||||||
node = getDOMNode(parent)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We're now hovering over something which does accept children.
|
// Find grid parent
|
||||||
// If it is empty, just go inside it.
|
const domComponent = getDOMNode(id)
|
||||||
if (empty) {
|
const gridId = domComponent?.closest(".grid")?.parentNode.dataset.id
|
||||||
dndStore.actions.updateDrop({
|
if (!gridId) {
|
||||||
parent: id,
|
|
||||||
index: 0,
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// As the first DOM node in a component may not necessarily contain the
|
// Update state
|
||||||
// child components, we can find to try the parent of the first child
|
dragInfo = {
|
||||||
// component and use that as the real parent DOM node
|
domTarget: e.target,
|
||||||
const childNode = node.getElementsByClassName("component")[0]
|
id,
|
||||||
if (childNode?.parentNode) {
|
gridId,
|
||||||
node = childNode.parentNode
|
mode,
|
||||||
|
side,
|
||||||
}
|
}
|
||||||
|
// builderStore.actions.selectComponent(dragInfo.id)
|
||||||
|
// builderStore.actions.setDragging(true)
|
||||||
|
|
||||||
// Append an ephemeral div to allow us to determine layout if only one
|
// Add event handler to clear all drag state when dragging ends
|
||||||
// child exists
|
dragInfo.domTarget.addEventListener("dragend", stopDragging)
|
||||||
let ephemeralDiv
|
|
||||||
if (node.children.length === 1) {
|
|
||||||
ephemeralDiv = document.createElement("div")
|
|
||||||
ephemeralDiv.dataset.id = DNDPlaceholderID
|
|
||||||
node.appendChild(ephemeralDiv)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We're now hovering over something which accepts children and is not
|
|
||||||
// empty, so we need to work out where to inside the placeholder
|
|
||||||
// Calculate the coordinates of various locations on each child.
|
|
||||||
const childCoords = [...(node.children || [])].map(node => {
|
|
||||||
const child = node.children?.[0] || node
|
|
||||||
const bounds = child.getBoundingClientRect()
|
|
||||||
return {
|
|
||||||
placeholder: node.dataset.id === DNDPlaceholderID,
|
|
||||||
centerX: bounds.left + bounds.width / 2,
|
|
||||||
centerY: bounds.top + bounds.height / 2,
|
|
||||||
left: bounds.left,
|
|
||||||
right: bounds.right,
|
|
||||||
top: bounds.top,
|
|
||||||
bottom: bounds.bottom,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Now that we've calculated the position of the children, we no longer need
|
|
||||||
// the ephemeral div
|
|
||||||
if (ephemeralDiv) {
|
|
||||||
node.removeChild(ephemeralDiv)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate the variance between each set of positions on the children
|
|
||||||
const variances = Object.keys(childCoords[0])
|
|
||||||
.filter(x => x !== "placeholder")
|
|
||||||
.map(key => {
|
|
||||||
const coords = childCoords.map(x => x[key])
|
|
||||||
return {
|
|
||||||
variance: variance(coords),
|
|
||||||
side: key,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Sort by variance. The lowest variance position indicates whether we are
|
|
||||||
// in a row or column layout
|
|
||||||
variances.sort((a, b) => {
|
|
||||||
return a.variance < b.variance ? -1 : 1
|
|
||||||
})
|
|
||||||
const column = ["centerX", "left", "right"].includes(variances[0].side)
|
|
||||||
|
|
||||||
// Calculate breakpoints between child components so we can determine the
|
|
||||||
// index to drop the component in.
|
|
||||||
// We want to ignore the placeholder from this calculation as it should not
|
|
||||||
// be considered a real child of the parent.
|
|
||||||
let breakpoints = childCoords
|
|
||||||
.filter(x => !x.placeholder)
|
|
||||||
.map(x => {
|
|
||||||
return column ? x.centerY : x.centerX
|
|
||||||
})
|
|
||||||
|
|
||||||
// Determine the index to drop the component in
|
|
||||||
const mousePosition = column ? mouseY : mouseX
|
|
||||||
let idx = 0
|
|
||||||
while (idx < breakpoints.length && breakpoints[idx] < mousePosition) {
|
|
||||||
idx++
|
|
||||||
}
|
|
||||||
dndStore.actions.updateDrop({
|
|
||||||
parent: id,
|
|
||||||
index: idx,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
const throttledProcessEvent = Utils.throttle(processEvent, ThrottleRate)
|
|
||||||
|
// Callback when entering a potential drop target
|
||||||
|
const onDragEnter = e => {
|
||||||
|
// Skip if we aren't validly dragging currently
|
||||||
|
if (!dragInfo || dragInfo.grid || !insideGrid(e)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const compDef = $componentStore.selectedComponent
|
||||||
|
const domGrid = getDOMNode(dragInfo.gridId)
|
||||||
|
if (domGrid) {
|
||||||
|
const getStyle = x => parseInt(compDef._styles.normal?.[x] || "0")
|
||||||
|
dragInfo.grid = {
|
||||||
|
startX: e.clientX,
|
||||||
|
startY: e.clientY,
|
||||||
|
rowStart: getStyle("grid-row-start"),
|
||||||
|
rowEnd: getStyle("grid-row-end"),
|
||||||
|
colStart: getStyle("grid-column-start"),
|
||||||
|
colEnd: getStyle("grid-column-end"),
|
||||||
|
rowDeltaMin: 1 - getStyle("grid-row-start"),
|
||||||
|
rowDeltaMax:
|
||||||
|
parseInt(domGrid.dataset.cols) + 1 - getStyle("grid-row-end"),
|
||||||
|
colDeltaMin: 1 - getStyle("grid-column-start"),
|
||||||
|
colDeltaMax:
|
||||||
|
parseInt(domGrid.dataset.cols) + 1 - getStyle("grid-column-end"),
|
||||||
|
}
|
||||||
|
handleEvent(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const processEvent = Utils.throttle((mouseX, mouseY) => {
|
||||||
|
if (!dragInfo?.grid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { mode, side, gridId, grid } = dragInfo
|
||||||
|
const {
|
||||||
|
startX,
|
||||||
|
startY,
|
||||||
|
rowStart,
|
||||||
|
rowEnd,
|
||||||
|
colStart,
|
||||||
|
colEnd,
|
||||||
|
rowDeltaMin,
|
||||||
|
rowDeltaMax,
|
||||||
|
colDeltaMin,
|
||||||
|
colDeltaMax,
|
||||||
|
} = grid
|
||||||
|
|
||||||
|
const domGrid = getDOMNode(gridId)
|
||||||
|
const cols = parseInt(domGrid.dataset.cols)
|
||||||
|
const { width, height } = domGrid.getBoundingClientRect()
|
||||||
|
|
||||||
|
const colWidth = width / cols
|
||||||
|
const diffX = mouseX - startX
|
||||||
|
let deltaX = Math.round(diffX / colWidth)
|
||||||
|
const rowHeight = height / cols
|
||||||
|
const diffY = mouseY - startY
|
||||||
|
let deltaY = Math.round(diffY / rowHeight)
|
||||||
|
|
||||||
|
if (mode === "move") {
|
||||||
|
deltaY = Math.min(Math.max(deltaY, rowDeltaMin), rowDeltaMax)
|
||||||
|
deltaX = Math.min(Math.max(deltaX, colDeltaMin), colDeltaMax)
|
||||||
|
gridStyles = {
|
||||||
|
"grid-row-start": rowStart + deltaY,
|
||||||
|
"grid-row-end": rowEnd + deltaY,
|
||||||
|
"grid-column-start": colStart + deltaX,
|
||||||
|
"grid-column-end": colEnd + deltaX,
|
||||||
|
}
|
||||||
|
} else if (mode === "resize") {
|
||||||
|
let newStyles = {}
|
||||||
|
if (side === "right") {
|
||||||
|
newStyles["grid-column-end"] = Math.max(colEnd + deltaX, colStart + 1)
|
||||||
|
} else if (side === "left") {
|
||||||
|
newStyles["grid-column-start"] = Math.min(colStart + deltaX, colEnd - 1)
|
||||||
|
} else if (side === "top") {
|
||||||
|
newStyles["grid-row-start"] = Math.min(rowStart + deltaY, rowEnd - 1)
|
||||||
|
} else if (side === "bottom") {
|
||||||
|
newStyles["grid-row-end"] = Math.max(rowEnd + deltaY, rowStart + 1)
|
||||||
|
} else if (side === "bottom-right") {
|
||||||
|
newStyles["grid-column-end"] = Math.max(colEnd + deltaX, colStart + 1)
|
||||||
|
newStyles["grid-row-end"] = Math.max(rowEnd + deltaY, rowStart + 1)
|
||||||
|
} else if (side === "bottom-left") {
|
||||||
|
newStyles["grid-column-start"] = Math.min(colStart + deltaX, colEnd - 1)
|
||||||
|
newStyles["grid-row-end"] = Math.max(rowEnd + deltaY, rowStart + 1)
|
||||||
|
} else if (side === "top-right") {
|
||||||
|
newStyles["grid-column-end"] = Math.max(colEnd + deltaX, colStart + 1)
|
||||||
|
newStyles["grid-row-start"] = Math.min(rowStart + deltaY, rowEnd - 1)
|
||||||
|
} else if (side === "top-left") {
|
||||||
|
newStyles["grid-column-start"] = Math.min(colStart + deltaX, colEnd - 1)
|
||||||
|
newStyles["grid-row-start"] = Math.min(rowStart + deltaY, rowEnd - 1)
|
||||||
|
}
|
||||||
|
gridStyles = newStyles
|
||||||
|
}
|
||||||
|
}, 100)
|
||||||
|
|
||||||
const handleEvent = e => {
|
const handleEvent = e => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
throttledProcessEvent(e.clientX, e.clientY)
|
e.stopPropagation()
|
||||||
|
processEvent(e.clientX, e.clientY)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback when on top of a component
|
|
||||||
const onDragOver = e => {
|
const onDragOver = e => {
|
||||||
// New stuff
|
if (!dragInfo?.grid || !insideGrid(e)) {
|
||||||
// // Skip if we aren't validly dragging currently
|
|
||||||
// if (!dragInfo) {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// e.preventDefault()
|
|
||||||
//
|
|
||||||
// // Set drag info for grids if not set
|
|
||||||
// if (!dragInfo.grid) {
|
|
||||||
// const coord = e.target.closest(".grid-coord")
|
|
||||||
// if (coord) {
|
|
||||||
// const row = parseInt(coord.dataset.row)
|
|
||||||
// const col = parseInt(coord.dataset.col)
|
|
||||||
// const component = $componentStore.selectedComponent
|
|
||||||
// const getStyle = x => parseInt(component._styles.normal?.[x] || "0")
|
|
||||||
// dragInfo.grid = {
|
|
||||||
// startRow: row,
|
|
||||||
// startCol: col,
|
|
||||||
// rowDeltaMin: 1 - getStyle("grid-row-start"),
|
|
||||||
// rowDeltaMax: 13 - getStyle("grid-row-end"),
|
|
||||||
// colDeltaMin: 1 - getStyle("grid-column-start"),
|
|
||||||
// colDeltaMax: 13 - getStyle("grid-column-end"),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (!dropInfo) {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// const { droppableInside, bounds } = dropInfo
|
|
||||||
// const { top, left, height, width } = bounds
|
|
||||||
// const mouseY = e.clientY
|
|
||||||
// const mouseX = e.clientX
|
|
||||||
// const snapFactor = droppableInside ? 0.33 : 0.5
|
|
||||||
// const snapLimitV = Math.min(40, height * snapFactor)
|
|
||||||
// const snapLimitH = Math.min(40, width * snapFactor)
|
|
||||||
//
|
|
||||||
// // Determine all sies we are within snap range of
|
|
||||||
// let sides = []
|
|
||||||
// if (mouseY <= top + snapLimitV) {
|
|
||||||
// sides.push(Sides.Top)
|
|
||||||
// } else if (mouseY >= top + height - snapLimitV) {
|
|
||||||
// sides.push(Sides.Bottom)
|
|
||||||
// }
|
|
||||||
// if (mouseX < left + snapLimitH) {
|
|
||||||
// sides.push(Sides.Left)
|
|
||||||
// } else if (mouseX > left + width - snapLimitH) {
|
|
||||||
// sides.push(Sides.Right)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // When no edges match, drop inside if possible
|
|
||||||
// if (!sides.length) {
|
|
||||||
// dropInfo.mode = droppableInside ? "inside" : null
|
|
||||||
// dropInfo.side = null
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // When one edge matches, use that edge
|
|
||||||
// if (sides.length === 1) {
|
|
||||||
// dropInfo.side = sides[0]
|
|
||||||
// if ([Sides.Top, Sides.Left].includes(sides[0])) {
|
|
||||||
// dropInfo.mode = "above"
|
|
||||||
// } else {
|
|
||||||
// dropInfo.mode = "below"
|
|
||||||
// }
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // When 2 edges match, work out which is closer
|
|
||||||
// const mousePoint = [mouseX, mouseY]
|
|
||||||
// const edges = getEdges(bounds, mousePoint)
|
|
||||||
// const edge1 = edges[sides[0]]
|
|
||||||
// const delta1 = calculatePointDelta(mousePoint, edge1)
|
|
||||||
// const edge2 = edges[sides[1]]
|
|
||||||
// const delta2 = calculatePointDelta(mousePoint, edge2)
|
|
||||||
// const edge = delta1 < delta2 ? sides[0] : sides[1]
|
|
||||||
// dropInfo.side = edge
|
|
||||||
// if ([Sides.Top, Sides.Left].includes(edge)) {
|
|
||||||
// dropInfo.mode = "above"
|
|
||||||
// } else {
|
|
||||||
// dropInfo.mode = "below"
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Current stuff
|
|
||||||
if (!source || !target) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
handleEvent(e)
|
handleEvent(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback when entering a potential drop target
|
|
||||||
const onDragEnter = e => {
|
|
||||||
// // New stuff
|
|
||||||
// const coord = e.target.closest(".grid-coord")
|
|
||||||
// if (coord && dragInfo.grid) {
|
|
||||||
// const row = parseInt(coord.dataset.row)
|
|
||||||
// const col = parseInt(coord.dataset.col)
|
|
||||||
// const { mode, side, grid } = dragInfo
|
|
||||||
// const {
|
|
||||||
// startRow,
|
|
||||||
// startCol,
|
|
||||||
// rowDeltaMin,
|
|
||||||
// rowDeltaMax,
|
|
||||||
// colDeltaMin,
|
|
||||||
// colDeltaMax,
|
|
||||||
// } = grid
|
|
||||||
//
|
|
||||||
// const component = $componentStore.selectedComponent
|
|
||||||
// const rowStart = parseInt(
|
|
||||||
// component._styles.normal?.["grid-row-start"] || 0
|
|
||||||
// )
|
|
||||||
// const rowEnd = parseInt(component._styles.normal?.["grid-row-end"] || 0)
|
|
||||||
// const colStart = parseInt(
|
|
||||||
// component._styles.normal?.["grid-column-start"] || 0
|
|
||||||
// )
|
|
||||||
// const colEnd = parseInt(
|
|
||||||
// component._styles.normal?.["grid-column-end"] || 0
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// let rowDelta = row - startRow
|
|
||||||
// let colDelta = col - startCol
|
|
||||||
//
|
|
||||||
// if (mode === "move") {
|
|
||||||
// rowDelta = Math.min(Math.max(rowDelta, rowDeltaMin), rowDeltaMax)
|
|
||||||
// colDelta = Math.min(Math.max(colDelta, colDeltaMin), colDeltaMax)
|
|
||||||
// builderStore.actions.setGridStyles({
|
|
||||||
// "grid-row-start": rowStart + rowDelta,
|
|
||||||
// "grid-row-end": rowEnd + rowDelta,
|
|
||||||
// "grid-column-start": colStart + colDelta,
|
|
||||||
// "grid-column-end": colEnd + colDelta,
|
|
||||||
// })
|
|
||||||
// } else if (mode === "resize") {
|
|
||||||
// let newStyles = {}
|
|
||||||
// if (side === "right") {
|
|
||||||
// newStyles["grid-column-end"] = colEnd + colDelta
|
|
||||||
// } else if (side === "left") {
|
|
||||||
// newStyles["grid-column-start"] = colStart + colDelta
|
|
||||||
// } else if (side === "top") {
|
|
||||||
// newStyles["grid-row-start"] = rowStart + rowDelta
|
|
||||||
// } else if (side === "bottom") {
|
|
||||||
// newStyles["grid-row-end"] = rowEnd + rowDelta
|
|
||||||
// } else if (side === "bottom-right") {
|
|
||||||
// newStyles["grid-column-end"] = colEnd + colDelta
|
|
||||||
// newStyles["grid-row-end"] = rowEnd + rowDelta
|
|
||||||
// } else if (side === "bottom-left") {
|
|
||||||
// newStyles["grid-column-start"] = colStart + colDelta
|
|
||||||
// newStyles["grid-row-end"] = rowEnd + rowDelta
|
|
||||||
// } else if (side === "top-right") {
|
|
||||||
// newStyles["grid-column-end"] = colEnd + colDelta
|
|
||||||
// newStyles["grid-row-start"] = rowStart + rowDelta
|
|
||||||
// } else if (side === "top-left") {
|
|
||||||
// newStyles["grid-column-start"] = colStart + colDelta
|
|
||||||
// newStyles["grid-row-start"] = rowStart + rowDelta
|
|
||||||
// }
|
|
||||||
// builderStore.actions.setGridStyles(newStyles)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return
|
|
||||||
//
|
|
||||||
// const element = e.target.closest(".component:not(.block)")
|
|
||||||
// if (
|
|
||||||
// element &&
|
|
||||||
// element.classList.contains("droppable") &&
|
|
||||||
// element.dataset.id !== dragInfo.target
|
|
||||||
// ) {
|
|
||||||
// // Do nothing if this is the same target
|
|
||||||
// if (element.dataset.id === dropInfo?.target) {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Ensure the dragging flag is always set.
|
|
||||||
// // There's a bit of a race condition between the app reinitialisation
|
|
||||||
// // after selecting the DND component and setting this the first time
|
|
||||||
// if (!get(builderStore).isDragging) {
|
|
||||||
// builderStore.actions.setDragging(true)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Store target 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,
|
|
||||||
// icon: element.dataset.icon,
|
|
||||||
// droppableInside: element.classList.contains("empty"),
|
|
||||||
// bounds,
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// dropInfo = null
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Current stuff
|
|
||||||
if (!source) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the next valid component to consider dropping over, ignoring nested
|
|
||||||
// block components
|
|
||||||
const component = e.target?.closest?.(
|
|
||||||
`.component:not(.block):not(.${source.id})`
|
|
||||||
)
|
|
||||||
if (component && component.classList.contains("droppable")) {
|
|
||||||
dndStore.actions.updateTarget({
|
|
||||||
id: component.dataset.id,
|
|
||||||
parent: component.dataset.parent,
|
|
||||||
node: getDOMNode(component.dataset.id),
|
|
||||||
empty: component.classList.contains("empty"),
|
|
||||||
acceptsChildren: component.classList.contains("parent"),
|
|
||||||
})
|
|
||||||
handleEvent(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callback when dropping a drag on top of some component
|
|
||||||
const onDrop = () => {
|
|
||||||
if (!source || !drop?.parent || drop?.index == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we're adding a new component rather than moving one
|
|
||||||
if (source.newComponentType) {
|
|
||||||
builderStore.actions.dropNewComponent(
|
|
||||||
source.newComponentType,
|
|
||||||
drop.parent,
|
|
||||||
drop.index
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert parent + index into target + mode
|
|
||||||
let legacyDropTarget, legacyDropMode
|
|
||||||
const parent = findComponentById(
|
|
||||||
get(screenStore).activeScreen?.props,
|
|
||||||
drop.parent
|
|
||||||
)
|
|
||||||
if (!parent) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do nothing if we didn't change the location
|
|
||||||
if (source.parent === drop.parent && source.index === drop.index) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter out source component and placeholder from consideration
|
|
||||||
const children = parent._children?.filter(
|
|
||||||
x => x._id !== DNDPlaceholderID && x._id !== source.id
|
|
||||||
)
|
|
||||||
|
|
||||||
// Use inside if no existing children
|
|
||||||
if (!children?.length) {
|
|
||||||
legacyDropTarget = parent._id
|
|
||||||
legacyDropMode = "inside"
|
|
||||||
} else if (drop.index === 0) {
|
|
||||||
legacyDropTarget = children[0]?._id
|
|
||||||
legacyDropMode = "above"
|
|
||||||
} else {
|
|
||||||
legacyDropTarget = children[drop.index - 1]?._id
|
|
||||||
legacyDropMode = "below"
|
|
||||||
}
|
|
||||||
|
|
||||||
if (legacyDropTarget && legacyDropMode) {
|
|
||||||
builderStore.actions.moveComponent(
|
|
||||||
source.id,
|
|
||||||
legacyDropTarget,
|
|
||||||
legacyDropMode
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
// Events fired on the draggable target
|
|
||||||
document.addEventListener("dragstart", onDragStart, false)
|
document.addEventListener("dragstart", onDragStart, false)
|
||||||
|
|
||||||
// Events fired on the drop targets
|
|
||||||
document.addEventListener("dragover", onDragOver, false)
|
|
||||||
document.addEventListener("dragenter", onDragEnter, false)
|
document.addEventListener("dragenter", onDragEnter, false)
|
||||||
document.addEventListener("drop", onDrop, false)
|
document.addEventListener("dragover", onDragOver, false)
|
||||||
})
|
})
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
// Events fired on the draggable target
|
|
||||||
document.removeEventListener("dragstart", onDragStart, false)
|
document.removeEventListener("dragstart", onDragStart, false)
|
||||||
|
|
||||||
// Events fired on the drop targets
|
|
||||||
document.removeEventListener("dragover", onDragOver, false)
|
|
||||||
document.removeEventListener("dragenter", onDragEnter, false)
|
document.removeEventListener("dragenter", onDragEnter, false)
|
||||||
document.removeEventListener("drop", onDrop, false)
|
document.removeEventListener("dragover", onDragOver, false)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<IndicatorSet
|
|
||||||
componentId={$dndParent}
|
|
||||||
color="var(--spectrum-global-color-static-green-500)"
|
|
||||||
zIndex="930"
|
|
||||||
transition
|
|
||||||
prefix="Inside"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{#if $dndIsDragging}
|
|
||||||
<DNDPlaceholderOverlay />
|
|
||||||
{/if}
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount, onDestroy } from "svelte"
|
import { onMount, onDestroy } from "svelte"
|
||||||
import IndicatorSet from "./IndicatorSet.svelte"
|
import IndicatorSet from "./IndicatorSet.svelte"
|
||||||
import { builderStore, dndIsDragging } from "stores"
|
import { builderStore } from "stores"
|
||||||
|
|
||||||
let componentId
|
let componentId
|
||||||
$: zIndex = componentId === $builderStore.selectedComponentId ? 900 : 920
|
$: zIndex = componentId === $builderStore.selectedComponentId ? 900 : 920
|
||||||
|
@ -30,7 +30,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<IndicatorSet
|
<IndicatorSet
|
||||||
componentId={$dndIsDragging ? null : componentId}
|
componentId={$builderStore.dragging ? null : componentId}
|
||||||
color="var(--spectrum-global-color-static-blue-200)"
|
color="var(--spectrum-global-color-static-blue-200)"
|
||||||
transition
|
transition
|
||||||
{zIndex}
|
{zIndex}
|
||||||
|
|
|
@ -10,10 +10,22 @@
|
||||||
export let icon
|
export let icon
|
||||||
export let color
|
export let color
|
||||||
export let zIndex
|
export let zIndex
|
||||||
|
export let componentId
|
||||||
export let transition = false
|
export let transition = false
|
||||||
export let line = false
|
export let line = false
|
||||||
export let alignRight = false
|
export let alignRight = false
|
||||||
export let componentId
|
export let showResizeAnchors = false
|
||||||
|
|
||||||
|
const AnchorSides = [
|
||||||
|
"right",
|
||||||
|
"left",
|
||||||
|
"top",
|
||||||
|
"bottom",
|
||||||
|
"bottom-right",
|
||||||
|
"bottom-left",
|
||||||
|
"top-right",
|
||||||
|
"top-left",
|
||||||
|
]
|
||||||
|
|
||||||
$: flipped = top < 24
|
$: flipped = top < 24
|
||||||
</script>
|
</script>
|
||||||
|
@ -41,54 +53,16 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div
|
{#if showResizeAnchors}
|
||||||
draggable={true}
|
{#each AnchorSides as side}
|
||||||
class="anchor right"
|
<div
|
||||||
data-side="right"
|
draggable="true"
|
||||||
data-id={componentId}
|
class="anchor {side}"
|
||||||
/>
|
data-side={side}
|
||||||
<div
|
data-id={componentId}
|
||||||
draggable={true}
|
/>
|
||||||
class="anchor top"
|
{/each}
|
||||||
data-side="top"
|
{/if}
|
||||||
data-id={componentId}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
draggable={true}
|
|
||||||
class="anchor left"
|
|
||||||
data-side="left"
|
|
||||||
data-id={componentId}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
draggable={true}
|
|
||||||
class="anchor bottom"
|
|
||||||
data-side="bottom"
|
|
||||||
data-id={componentId}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
draggable={true}
|
|
||||||
class="anchor bottom-right"
|
|
||||||
data-side="bottom-right"
|
|
||||||
data-id={componentId}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
draggable={true}
|
|
||||||
class="anchor bottom-left"
|
|
||||||
data-side="bottom-left"
|
|
||||||
data-id={componentId}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
draggable={true}
|
|
||||||
class="anchor top-right"
|
|
||||||
data-side="top-right"
|
|
||||||
data-id={componentId}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
draggable={true}
|
|
||||||
class="anchor top-left"
|
|
||||||
data-side="top-left"
|
|
||||||
data-id={componentId}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -9,11 +9,13 @@
|
||||||
export let transition
|
export let transition
|
||||||
export let zIndex
|
export let zIndex
|
||||||
export let prefix = null
|
export let prefix = null
|
||||||
|
export let allowResizeAnchors = false
|
||||||
|
|
||||||
let indicators = []
|
let indicators = []
|
||||||
let interval
|
let interval
|
||||||
let text
|
let text
|
||||||
let icon
|
let icon
|
||||||
|
let insideGrid = false
|
||||||
|
|
||||||
$: visibleIndicators = indicators.filter(x => x.visible)
|
$: visibleIndicators = indicators.filter(x => x.visible)
|
||||||
$: offset = $builderStore.inBuilder ? 0 : 2
|
$: offset = $builderStore.inBuilder ? 0 : 2
|
||||||
|
@ -23,6 +25,19 @@
|
||||||
let callbackCount = 0
|
let callbackCount = 0
|
||||||
let nextIndicators = []
|
let nextIndicators = []
|
||||||
|
|
||||||
|
const checkInsideGrid = id => {
|
||||||
|
const component = document.getElementsByClassName(id)[0]
|
||||||
|
const domNode = component?.children[0]
|
||||||
|
|
||||||
|
// Ignore grid itself
|
||||||
|
if (domNode?.classList.contains("grid")) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we're a descendent of a grid
|
||||||
|
return domNode?.closest(".grid") != null
|
||||||
|
}
|
||||||
|
|
||||||
const createIntersectionCallback = idx => entries => {
|
const createIntersectionCallback = idx => entries => {
|
||||||
if (callbackCount >= observers.length) {
|
if (callbackCount >= observers.length) {
|
||||||
return
|
return
|
||||||
|
@ -52,6 +67,11 @@
|
||||||
observers = []
|
observers = []
|
||||||
nextIndicators = []
|
nextIndicators = []
|
||||||
|
|
||||||
|
// Check if we're inside a grid
|
||||||
|
if (allowResizeAnchors) {
|
||||||
|
insideGrid = checkInsideGrid(componentId)
|
||||||
|
}
|
||||||
|
|
||||||
// Determine next set of indicators
|
// Determine next set of indicators
|
||||||
const parents = document.getElementsByClassName(componentId)
|
const parents = document.getElementsByClassName(componentId)
|
||||||
if (parents.length) {
|
if (parents.length) {
|
||||||
|
@ -127,6 +147,7 @@
|
||||||
height={indicator.height}
|
height={indicator.height}
|
||||||
text={idx === 0 ? text : null}
|
text={idx === 0 ? text : null}
|
||||||
icon={idx === 0 ? icon : null}
|
icon={idx === 0 ? icon : null}
|
||||||
|
showResizeAnchors={allowResizeAnchors && insideGrid}
|
||||||
{componentId}
|
{componentId}
|
||||||
{transition}
|
{transition}
|
||||||
{zIndex}
|
{zIndex}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { builderStore, dndIsDragging } from "stores"
|
import { builderStore } from "stores"
|
||||||
import IndicatorSet from "./IndicatorSet.svelte"
|
import IndicatorSet from "./IndicatorSet.svelte"
|
||||||
|
|
||||||
$: color = $builderStore.editMode
|
$: color = $builderStore.editMode
|
||||||
|
@ -8,8 +8,9 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<IndicatorSet
|
<IndicatorSet
|
||||||
componentId={$dndIsDragging ? null : $builderStore.selectedComponentId}
|
componentId={$builderStore.selectedComponentId}
|
||||||
{color}
|
{color}
|
||||||
zIndex="910"
|
zIndex="910"
|
||||||
transition
|
transition
|
||||||
|
allowResizeAnchors
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import SettingsButton from "./SettingsButton.svelte"
|
import SettingsButton from "./SettingsButton.svelte"
|
||||||
import SettingsColorPicker from "./SettingsColorPicker.svelte"
|
import SettingsColorPicker from "./SettingsColorPicker.svelte"
|
||||||
import SettingsPicker from "./SettingsPicker.svelte"
|
import SettingsPicker from "./SettingsPicker.svelte"
|
||||||
import { builderStore, componentStore, dndIsDragging } from "stores"
|
import { builderStore, componentStore } from "stores"
|
||||||
import { domDebounce } from "utils/domDebounce"
|
import { domDebounce } from "utils/domDebounce"
|
||||||
|
|
||||||
const verticalOffset = 36
|
const verticalOffset = 36
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
let measured = false
|
let measured = false
|
||||||
|
|
||||||
$: definition = $componentStore.selectedComponentDefinition
|
$: definition = $componentStore.selectedComponentDefinition
|
||||||
$: showBar = definition?.showSettingsBar && !$dndIsDragging
|
$: showBar = definition?.showSettingsBar && !$builderStore.dragging
|
||||||
$: settings = getBarSettings(definition)
|
$: settings = getBarSettings(definition)
|
||||||
|
|
||||||
const getBarSettings = definition => {
|
const getBarSettings = definition => {
|
||||||
|
|
|
@ -41,8 +41,6 @@ const loadBudibase = async () => {
|
||||||
previewDevice: window["##BUDIBASE_PREVIEW_DEVICE##"],
|
previewDevice: window["##BUDIBASE_PREVIEW_DEVICE##"],
|
||||||
navigation: window["##BUDIBASE_PREVIEW_NAVIGATION##"],
|
navigation: window["##BUDIBASE_PREVIEW_NAVIGATION##"],
|
||||||
hiddenComponentIds: window["##BUDIBASE_HIDDEN_COMPONENT_IDS##"],
|
hiddenComponentIds: window["##BUDIBASE_HIDDEN_COMPONENT_IDS##"],
|
||||||
gridStyles: get(builderStore).gridStyles,
|
|
||||||
isDragging: get(builderStore).isDragging,
|
|
||||||
usedPlugins: window["##BUDIBASE_USED_PLUGINS##"],
|
usedPlugins: window["##BUDIBASE_USED_PLUGINS##"],
|
||||||
location: window["##BUDIBASE_LOCATION##"],
|
location: window["##BUDIBASE_LOCATION##"],
|
||||||
})
|
})
|
||||||
|
|
|
@ -19,6 +19,7 @@ const createBuilderStore = () => {
|
||||||
navigation: null,
|
navigation: null,
|
||||||
hiddenComponentIds: [],
|
hiddenComponentIds: [],
|
||||||
usedPlugins: null,
|
usedPlugins: null,
|
||||||
|
dragging: false,
|
||||||
|
|
||||||
// Legacy - allow the builder to specify a layout
|
// Legacy - allow the builder to specify a layout
|
||||||
layout: null,
|
layout: null,
|
||||||
|
@ -111,6 +112,12 @@ 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")
|
||||||
},
|
},
|
||||||
|
setDragging: dragging => {
|
||||||
|
store.update(state => {
|
||||||
|
state.dragging = dragging
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...store,
|
...store,
|
||||||
|
|
|
@ -10,9 +10,6 @@ const createDndStore = () => {
|
||||||
|
|
||||||
// Info about where the component would be dropped
|
// Info about where the component would be dropped
|
||||||
drop: null,
|
drop: null,
|
||||||
|
|
||||||
// Grid info
|
|
||||||
gridStyles: null,
|
|
||||||
}
|
}
|
||||||
const store = writable(initialState)
|
const store = writable(initialState)
|
||||||
|
|
||||||
|
@ -62,13 +59,6 @@ const createDndStore = () => {
|
||||||
store.set(initialState)
|
store.set(initialState)
|
||||||
}
|
}
|
||||||
|
|
||||||
const setGridStyles = styles => {
|
|
||||||
store.update(state => {
|
|
||||||
state.gridStyles = styles
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subscribe: store.subscribe,
|
subscribe: store.subscribe,
|
||||||
actions: {
|
actions: {
|
||||||
|
@ -77,7 +67,6 @@ const createDndStore = () => {
|
||||||
updateTarget,
|
updateTarget,
|
||||||
updateDrop,
|
updateDrop,
|
||||||
reset,
|
reset,
|
||||||
setGridStyles,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,7 +77,6 @@ export const dndStore = createDndStore()
|
||||||
// performance by deriving any state that needs to be externally observed.
|
// performance by deriving any state that needs to be externally observed.
|
||||||
// By doing this and using primitives, we can avoid invalidating other stores
|
// By doing this and using primitives, we can avoid invalidating other stores
|
||||||
// or components which depend on DND state unless values actually change.
|
// or components which depend on DND state unless values actually change.
|
||||||
export const dndIsDragging = derived(dndStore, $dndStore => !!$dndStore.source)
|
|
||||||
export const dndParent = derived(dndStore, $dndStore => $dndStore.drop?.parent)
|
export const dndParent = derived(dndStore, $dndStore => $dndStore.drop?.parent)
|
||||||
export const dndIndex = derived(dndStore, $dndStore => $dndStore.drop?.index)
|
export const dndIndex = derived(dndStore, $dndStore => $dndStore.drop?.index)
|
||||||
export const dndBounds = derived(
|
export const dndBounds = derived(
|
||||||
|
|
|
@ -21,7 +21,6 @@ export {
|
||||||
dndParent,
|
dndParent,
|
||||||
dndBounds,
|
dndBounds,
|
||||||
dndIsNewComponent,
|
dndIsNewComponent,
|
||||||
dndIsDragging,
|
|
||||||
} from "./dnd"
|
} from "./dnd"
|
||||||
|
|
||||||
// Context stores are layered and duplicated, so it is not a singleton
|
// Context stores are layered and duplicated, so it is not a singleton
|
||||||
|
|
Loading…
Reference in New Issue