Add ability to drag new components into the preview
This commit is contained in:
parent
2c8dd09a56
commit
d997afffc0
|
@ -1,6 +1,6 @@
|
||||||
import { get, writable } from "svelte/store"
|
import { get, writable } from "svelte/store"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { selectedScreen, selectedComponent } from "builderStore"
|
import { selectedScreen, selectedComponent, store } from "builderStore"
|
||||||
import {
|
import {
|
||||||
datasources,
|
datasources,
|
||||||
integrations,
|
integrations,
|
||||||
|
@ -451,7 +451,7 @@ export const getFrontendStore = () => {
|
||||||
...extras,
|
...extras,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
create: async (componentName, presetProps) => {
|
create: async (componentName, presetProps, parent, index) => {
|
||||||
const state = get(store)
|
const state = get(store)
|
||||||
const componentInstance = store.actions.components.createInstance(
|
const componentInstance = store.actions.components.createInstance(
|
||||||
componentName,
|
componentName,
|
||||||
|
@ -461,48 +461,62 @@ export const getFrontendStore = () => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Patch selected screen
|
// Insert in position if specified
|
||||||
await store.actions.screens.patch(screen => {
|
if (parent && index != null) {
|
||||||
// Find the selected component
|
await store.actions.screens.patch(screen => {
|
||||||
const currentComponent = findComponent(
|
let parentComponent = findComponent(screen.props, parent)
|
||||||
screen.props,
|
if (!parentComponent._children?.length) {
|
||||||
state.selectedComponentId
|
parentComponent._children = [componentInstance]
|
||||||
)
|
|
||||||
if (!currentComponent) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find parent node to attach this component to
|
|
||||||
let parentComponent
|
|
||||||
if (currentComponent) {
|
|
||||||
// Use selected component as parent if one is selected
|
|
||||||
const definition = store.actions.components.getDefinition(
|
|
||||||
currentComponent._component
|
|
||||||
)
|
|
||||||
if (definition?.hasChildren) {
|
|
||||||
// Use selected component if it allows children
|
|
||||||
parentComponent = currentComponent
|
|
||||||
} else {
|
} else {
|
||||||
// Otherwise we need to use the parent of this component
|
parentComponent._children.splice(index, 0, componentInstance)
|
||||||
parentComponent = findComponentParent(
|
|
||||||
screen.props,
|
|
||||||
currentComponent._id
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else {
|
})
|
||||||
// Use screen or layout if no component is selected
|
}
|
||||||
parentComponent = screen.props
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attach new component
|
// Otherwise we work out where this component should be inserted
|
||||||
if (!parentComponent) {
|
else {
|
||||||
return false
|
await store.actions.screens.patch(screen => {
|
||||||
}
|
// Find the selected component
|
||||||
if (!parentComponent._children) {
|
const currentComponent = findComponent(
|
||||||
parentComponent._children = []
|
screen.props,
|
||||||
}
|
state.selectedComponentId
|
||||||
parentComponent._children.push(componentInstance)
|
)
|
||||||
})
|
if (!currentComponent) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find parent node to attach this component to
|
||||||
|
let parentComponent
|
||||||
|
if (currentComponent) {
|
||||||
|
// Use selected component as parent if one is selected
|
||||||
|
const definition = store.actions.components.getDefinition(
|
||||||
|
currentComponent._component
|
||||||
|
)
|
||||||
|
if (definition?.hasChildren) {
|
||||||
|
// Use selected component if it allows children
|
||||||
|
parentComponent = currentComponent
|
||||||
|
} else {
|
||||||
|
// Otherwise we need to use the parent of this component
|
||||||
|
parentComponent = findComponentParent(
|
||||||
|
screen.props,
|
||||||
|
currentComponent._id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Use screen or layout if no component is selected
|
||||||
|
parentComponent = screen.props
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach new component
|
||||||
|
if (!parentComponent) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (!parentComponent._children) {
|
||||||
|
parentComponent._children = []
|
||||||
|
}
|
||||||
|
parentComponent._children.push(componentInstance)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Select new component
|
// Select new component
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
|
@ -990,6 +1004,19 @@ export const getFrontendStore = () => {
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
dnd: {
|
||||||
|
start: component => {
|
||||||
|
store.actions.preview.sendEvent("dragging-new-component", {
|
||||||
|
dragging: true,
|
||||||
|
component,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
stop: () => {
|
||||||
|
store.actions.preview.sendEvent("dragging-new-component", {
|
||||||
|
dragging: false,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return store
|
return store
|
||||||
|
|
|
@ -213,6 +213,9 @@
|
||||||
await store.actions.components.handleEjectBlock(id, definition)
|
await store.actions.components.handleEjectBlock(id, definition)
|
||||||
} else if (type === "reload-plugin") {
|
} else if (type === "reload-plugin") {
|
||||||
await store.actions.components.refreshDefinitions()
|
await store.actions.components.refreshDefinitions()
|
||||||
|
} else if (type === "drop-new-component") {
|
||||||
|
const { component, parent, index } = data
|
||||||
|
await store.actions.components.create(component, null, parent, index)
|
||||||
} else {
|
} else {
|
||||||
console.warn(`Client sent unknown event type: ${type}`)
|
console.warn(`Client sent unknown event type: ${type}`)
|
||||||
}
|
}
|
||||||
|
|
|
@ -169,6 +169,14 @@
|
||||||
window.removeEventListener("keydown", handleKeyDown)
|
window.removeEventListener("keydown", handleKeyDown)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const onDragStart = component => {
|
||||||
|
store.actions.dnd.start(component)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDragEnd = () => {
|
||||||
|
store.actions.dnd.stop()
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container" transition:fly|local={{ x: 260, duration: 300 }}>
|
<div class="container" transition:fly|local={{ x: 260, duration: 300 }}>
|
||||||
|
@ -206,6 +214,9 @@
|
||||||
<div class="category-label">{category.name}</div>
|
<div class="category-label">{category.name}</div>
|
||||||
{#each category.children as component}
|
{#each category.children as component}
|
||||||
<div
|
<div
|
||||||
|
draggable="true"
|
||||||
|
on:dragstart={() => onDragStart(component.component)}
|
||||||
|
on:dragend={onDragEnd}
|
||||||
data-cy={`component-${component.name}`}
|
data-cy={`component-${component.name}`}
|
||||||
class="component"
|
class="component"
|
||||||
class:selected={selectedIndex ===
|
class:selected={selectedIndex ===
|
||||||
|
|
|
@ -2,21 +2,22 @@
|
||||||
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 { builderStore, componentStore } from "stores"
|
import {
|
||||||
|
builderStore,
|
||||||
|
componentStore,
|
||||||
|
dndStore,
|
||||||
|
dndParent,
|
||||||
|
isDragging,
|
||||||
|
} 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"
|
||||||
|
|
||||||
let sourceInfo
|
// Cache some dnd store state as local variables as it massively helps
|
||||||
let targetInfo
|
// performance. It lets us avoid calling svelte getters on every DOM action.
|
||||||
let dropInfo
|
$: source = $dndStore.source
|
||||||
|
$: target = $dndStore.target
|
||||||
// These reactive statements are just a trick to only update the store when
|
$: drop = $dndStore.drop
|
||||||
// the value of one of the properties actually changes
|
|
||||||
$: parent = dropInfo?.parent
|
|
||||||
$: index = dropInfo?.index
|
|
||||||
$: bounds = sourceInfo?.bounds
|
|
||||||
$: builderStore.actions.updateDNDPlaceholder(parent, index, bounds)
|
|
||||||
|
|
||||||
// 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 => {
|
||||||
|
@ -37,19 +38,16 @@
|
||||||
|
|
||||||
// Callback when drag stops (whether dropped or not)
|
// Callback when drag stops (whether dropped or not)
|
||||||
const stopDragging = () => {
|
const stopDragging = () => {
|
||||||
// Reset state
|
|
||||||
sourceInfo = null
|
|
||||||
targetInfo = null
|
|
||||||
dropInfo = null
|
|
||||||
builderStore.actions.setDragging(false)
|
|
||||||
|
|
||||||
// Reset listener
|
// Reset listener
|
||||||
if (sourceInfo) {
|
if (source?.id) {
|
||||||
const component = document.getElementsByClassName(sourceInfo.id)[0]
|
const component = document.getElementsByClassName(source?.id)[0]
|
||||||
if (component) {
|
if (component) {
|
||||||
component.removeEventListener("dragend", stopDragging)
|
component.removeEventListener("dragend", stopDragging)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset state
|
||||||
|
dndStore.actions.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback when initially starting a drag on a draggable component
|
// Callback when initially starting a drag on a draggable component
|
||||||
|
@ -66,6 +64,7 @@
|
||||||
component.addEventListener("dragend", stopDragging)
|
component.addEventListener("dragend", stopDragging)
|
||||||
|
|
||||||
// Update state
|
// Update state
|
||||||
|
const id = component.dataset.id
|
||||||
const parentId = component.dataset.parent
|
const parentId = component.dataset.parent
|
||||||
const parent = findComponentById(
|
const parent = findComponentById(
|
||||||
get(componentStore).currentAsset.props,
|
get(componentStore).currentAsset.props,
|
||||||
|
@ -74,14 +73,13 @@
|
||||||
const index = parent._children.findIndex(
|
const index = parent._children.findIndex(
|
||||||
x => x._id === component.dataset.id
|
x => x._id === component.dataset.id
|
||||||
)
|
)
|
||||||
sourceInfo = {
|
dndStore.actions.startDragging({
|
||||||
id: component.dataset.id,
|
id,
|
||||||
bounds: component.children[0].getBoundingClientRect(),
|
bounds: component.children[0].getBoundingClientRect(),
|
||||||
parent: parentId,
|
parent: parentId,
|
||||||
index,
|
index,
|
||||||
}
|
})
|
||||||
builderStore.actions.selectComponent(sourceInfo.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.
|
||||||
|
@ -89,20 +87,20 @@
|
||||||
// the same handler as selecting a new component (which causes a client
|
// the same handler as selecting a new component (which causes a client
|
||||||
// re-initialisation).
|
// re-initialisation).
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
dropInfo = {
|
dndStore.actions.updateDrop({
|
||||||
parent: parentId,
|
parent: parentId,
|
||||||
index,
|
index,
|
||||||
}
|
})
|
||||||
}, 0)
|
}, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 = (mouseX, mouseY) => {
|
||||||
if (!targetInfo) {
|
if (!target) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
let { id, parent, node, acceptsChildren, empty } = targetInfo
|
let { id, parent, node, acceptsChildren, empty } = target
|
||||||
|
|
||||||
// If we're over something that does not accept children then we go up a
|
// 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
|
// level and consider the mouse position relative to the parent
|
||||||
|
@ -115,10 +113,10 @@
|
||||||
// We're now hovering over something which does accept children.
|
// We're now hovering over something which does accept children.
|
||||||
// If it is empty, just go inside it.
|
// If it is empty, just go inside it.
|
||||||
if (empty) {
|
if (empty) {
|
||||||
dropInfo = {
|
dndStore.actions.updateDrop({
|
||||||
parent: id,
|
parent: id,
|
||||||
index: 0,
|
index: 0,
|
||||||
}
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,10 +186,10 @@
|
||||||
while (idx < breakpoints.length && breakpoints[idx] < mousePosition) {
|
while (idx < breakpoints.length && breakpoints[idx] < mousePosition) {
|
||||||
idx++
|
idx++
|
||||||
}
|
}
|
||||||
dropInfo = {
|
dndStore.actions.updateDrop({
|
||||||
parent: id,
|
parent: id,
|
||||||
index: idx,
|
index: idx,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
const throttledProcessEvent = Utils.throttle(processEvent, 130)
|
const throttledProcessEvent = Utils.throttle(processEvent, 130)
|
||||||
|
|
||||||
|
@ -202,7 +200,7 @@
|
||||||
|
|
||||||
// Callback when on top of a component
|
// Callback when on top of a component
|
||||||
const onDragOver = e => {
|
const onDragOver = e => {
|
||||||
if (!sourceInfo || !targetInfo) {
|
if (!source || !target) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
handleEvent(e)
|
handleEvent(e)
|
||||||
|
@ -210,69 +208,80 @@
|
||||||
|
|
||||||
// Callback when entering a potential drop target
|
// Callback when entering a potential drop target
|
||||||
const onDragEnter = e => {
|
const onDragEnter = e => {
|
||||||
if (!sourceInfo) {
|
if (!source) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the next valid component to consider dropping over, ignoring nested
|
// Find the next valid component to consider dropping over, ignoring nested
|
||||||
// block components
|
// block components
|
||||||
const component = e.target?.closest?.(
|
const component = e.target?.closest?.(
|
||||||
`.component:not(.block):not(.${sourceInfo.id})`
|
`.component:not(.block):not(.${source.id})`
|
||||||
)
|
)
|
||||||
if (component && component.classList.contains("droppable")) {
|
if (component && component.classList.contains("droppable")) {
|
||||||
targetInfo = {
|
dndStore.actions.updateTarget({
|
||||||
id: component.dataset.id,
|
id: component.dataset.id,
|
||||||
parent: component.dataset.parent,
|
parent: component.dataset.parent,
|
||||||
node: getDOMNode(component.dataset.id),
|
node: getDOMNode(component.dataset.id),
|
||||||
empty: component.classList.contains("empty"),
|
empty: component.classList.contains("empty"),
|
||||||
acceptsChildren: component.classList.contains("parent"),
|
acceptsChildren: component.classList.contains("parent"),
|
||||||
}
|
})
|
||||||
handleEvent(e)
|
handleEvent(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback when dropping a drag on top of some component
|
// Callback when dropping a drag on top of some component
|
||||||
const onDrop = () => {
|
const onDrop = () => {
|
||||||
let target, mode
|
if (!source || !drop?.parent || drop?.index == null) {
|
||||||
|
return
|
||||||
// Convert parent + index into target + mode
|
|
||||||
if (sourceInfo && dropInfo?.parent && dropInfo.index != null) {
|
|
||||||
const parent = findComponentById(
|
|
||||||
get(componentStore).currentAsset?.props,
|
|
||||||
dropInfo.parent
|
|
||||||
)
|
|
||||||
if (!parent) {
|
|
||||||
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 !== sourceInfo.id
|
|
||||||
)
|
|
||||||
|
|
||||||
// Use inside if no existing children
|
|
||||||
if (!children?.length) {
|
|
||||||
target = parent._id
|
|
||||||
mode = "inside"
|
|
||||||
} else if (dropInfo.index === 0) {
|
|
||||||
target = children[0]?._id
|
|
||||||
mode = "above"
|
|
||||||
} else {
|
|
||||||
target = children[dropInfo.index - 1]?._id
|
|
||||||
mode = "below"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target && mode) {
|
// Check if we're adding a new component rather than moving one
|
||||||
builderStore.actions.moveComponent(sourceInfo.id, target, mode)
|
if (source.newComponentType) {
|
||||||
|
builderStore.actions.dropNewComponent(
|
||||||
|
source.newComponentType,
|
||||||
|
drop.parent,
|
||||||
|
drop.index
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert parent + index into target + mode
|
||||||
|
let legacyDropTarget, legacyDropMode
|
||||||
|
const parent = findComponentById(
|
||||||
|
get(componentStore).currentAsset?.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 !== "placeholder" && 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
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -298,13 +307,13 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<IndicatorSet
|
<IndicatorSet
|
||||||
componentId={$builderStore.dndParent}
|
componentId={$dndParent}
|
||||||
color="var(--spectrum-global-color-static-green-500)"
|
color="var(--spectrum-global-color-static-green-500)"
|
||||||
zIndex="930"
|
zIndex="930"
|
||||||
transition
|
transition
|
||||||
prefix="Inside"
|
prefix="Inside"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{#if $builderStore.isDragging}
|
{#if $isDragging}
|
||||||
<DNDPlaceholderOverlay />
|
<DNDPlaceholderOverlay />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<script>
|
<script>
|
||||||
import { builderStore } from "stores"
|
import { dndBounds } from "stores"
|
||||||
import { DNDPlaceholderID } from "constants"
|
import { DNDPlaceholderID } from "constants"
|
||||||
|
|
||||||
$: style = getStyle($builderStore.dndBounds)
|
$: style = getStyle($dndBounds)
|
||||||
|
|
||||||
const getStyle = bounds => {
|
const getStyle = bounds => {
|
||||||
if (!bounds) {
|
if (!bounds) {
|
||||||
|
|
|
@ -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 } from "stores"
|
import { builderStore, isDragging } 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={$builderStore.isDragging ? null : componentId}
|
componentId={$isDragging ? null : componentId}
|
||||||
color="var(--spectrum-global-color-static-blue-200)"
|
color="var(--spectrum-global-color-static-blue-200)"
|
||||||
transition
|
transition
|
||||||
{zIndex}
|
{zIndex}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { builderStore } from "stores"
|
import { builderStore, isDragging } from "stores"
|
||||||
import IndicatorSet from "./IndicatorSet.svelte"
|
import IndicatorSet from "./IndicatorSet.svelte"
|
||||||
|
|
||||||
$: color = $builderStore.editMode
|
$: color = $builderStore.editMode
|
||||||
|
@ -8,7 +8,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<IndicatorSet
|
<IndicatorSet
|
||||||
componentId={$builderStore.selectedComponentId}
|
componentId={$isDragging ? null : $builderStore.selectedComponentId}
|
||||||
{color}
|
{color}
|
||||||
zIndex="910"
|
zIndex="910"
|
||||||
transition
|
transition
|
||||||
|
|
|
@ -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 } from "stores"
|
import { builderStore, componentStore, isDragging } 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 && !$builderStore.isDragging
|
$: showBar = definition?.showSettingsBar && !$isDragging
|
||||||
$: settings = getBarSettings(definition)
|
$: settings = getBarSettings(definition)
|
||||||
|
|
||||||
const getBarSettings = definition => {
|
const getBarSettings = definition => {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
blockStore,
|
blockStore,
|
||||||
componentStore,
|
componentStore,
|
||||||
environmentStore,
|
environmentStore,
|
||||||
|
dndStore,
|
||||||
} from "./stores"
|
} from "./stores"
|
||||||
import loadSpectrumIcons from "@budibase/bbui/spectrum-icons-rollup.js"
|
import loadSpectrumIcons from "@budibase/bbui/spectrum-icons-rollup.js"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
|
@ -60,6 +61,24 @@ const loadBudibase = async () => {
|
||||||
if (name === "eject-block") {
|
if (name === "eject-block") {
|
||||||
const block = blockStore.actions.getBlock(payload)
|
const block = blockStore.actions.getBlock(payload)
|
||||||
block?.eject()
|
block?.eject()
|
||||||
|
} else if (name === "dragging-new-component") {
|
||||||
|
const { dragging, component } = payload
|
||||||
|
if (dragging) {
|
||||||
|
dndStore.actions.startDragging({
|
||||||
|
id: null,
|
||||||
|
parent: null,
|
||||||
|
bounds: {
|
||||||
|
height: 64,
|
||||||
|
width: 64,
|
||||||
|
},
|
||||||
|
index: null,
|
||||||
|
newComponentType: component,
|
||||||
|
})
|
||||||
|
builderStore.actions.setDraggingNewComponent(true)
|
||||||
|
} else {
|
||||||
|
dndStore.actions.reset()
|
||||||
|
builderStore.actions.setDraggingNewComponent(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,13 +16,10 @@ const createBuilderStore = () => {
|
||||||
theme: null,
|
theme: null,
|
||||||
customTheme: null,
|
customTheme: null,
|
||||||
previewDevice: "desktop",
|
previewDevice: "desktop",
|
||||||
isDragging: false,
|
draggingNewComponent: false,
|
||||||
navigation: null,
|
navigation: null,
|
||||||
hiddenComponentIds: [],
|
hiddenComponentIds: [],
|
||||||
usedPlugins: null,
|
usedPlugins: null,
|
||||||
dndParent: null,
|
|
||||||
dndIndex: null,
|
|
||||||
dndBounds: null,
|
|
||||||
|
|
||||||
// Legacy - allow the builder to specify a layout
|
// Legacy - allow the builder to specify a layout
|
||||||
layout: null,
|
layout: null,
|
||||||
|
@ -70,11 +67,19 @@ const createBuilderStore = () => {
|
||||||
mode,
|
mode,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
setDragging: dragging => {
|
dropNewComponent: (component, parent, index) => {
|
||||||
if (dragging === get(store).isDragging) {
|
console.log("dispatch", component, parent, index)
|
||||||
|
dispatchEvent("drop-new-component", {
|
||||||
|
component,
|
||||||
|
parent,
|
||||||
|
index,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
setDraggingNewComponent: draggingNewComponent => {
|
||||||
|
if (draggingNewComponent === get(store).draggingNewComponent) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
store.update(state => ({ ...state, isDragging: dragging }))
|
store.update(state => ({ ...state, draggingNewComponent }))
|
||||||
},
|
},
|
||||||
setEditMode: enabled => {
|
setEditMode: enabled => {
|
||||||
if (enabled === get(store).editMode) {
|
if (enabled === get(store).editMode) {
|
||||||
|
@ -111,14 +116,6 @@ 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")
|
||||||
},
|
},
|
||||||
updateDNDPlaceholder: (parent, index, bounds) => {
|
|
||||||
store.update(state => {
|
|
||||||
state.dndParent = parent
|
|
||||||
state.dndIndex = index
|
|
||||||
state.dndBounds = bounds
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...store,
|
...store,
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { findComponentById, findComponentPathById } from "../utils/components"
|
||||||
import { devToolsStore } from "./devTools"
|
import { devToolsStore } from "./devTools"
|
||||||
import { screenStore } from "./screens"
|
import { screenStore } from "./screens"
|
||||||
import { builderStore } from "./builder"
|
import { builderStore } from "./builder"
|
||||||
|
import { dndParent } from "./dnd.js"
|
||||||
import Router from "../components/Router.svelte"
|
import Router from "../components/Router.svelte"
|
||||||
import DNDPlaceholder from "../components/preview/DNDPlaceholder.svelte"
|
import DNDPlaceholder from "../components/preview/DNDPlaceholder.svelte"
|
||||||
import * as AppComponents from "../components/app/index.js"
|
import * as AppComponents from "../components/app/index.js"
|
||||||
|
@ -19,8 +20,8 @@ const createComponentStore = () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const derivedStore = derived(
|
const derivedStore = derived(
|
||||||
[store, builderStore, devToolsStore, screenStore],
|
[store, builderStore, devToolsStore, screenStore, dndParent],
|
||||||
([$store, $builderState, $devToolsState, $screenState]) => {
|
([$store, $builderState, $devToolsState, $screenState, $dndParent]) => {
|
||||||
// Avoid any of this logic if we aren't in the builder preview
|
// Avoid any of this logic if we aren't in the builder preview
|
||||||
if (!$builderState.inBuilder && !$devToolsState.visible) {
|
if (!$builderState.inBuilder && !$devToolsState.visible) {
|
||||||
return {}
|
return {}
|
||||||
|
@ -40,8 +41,7 @@ const createComponentStore = () => {
|
||||||
// Derive the selected component path
|
// Derive the selected component path
|
||||||
const selectedPath =
|
const selectedPath =
|
||||||
findComponentPathById(asset?.props, selectedComponentId) || []
|
findComponentPathById(asset?.props, selectedComponentId) || []
|
||||||
const dndPath =
|
const dndPath = findComponentPathById(asset?.props, $dndParent) || []
|
||||||
findComponentPathById(asset?.props, $builderState.dndParent) || []
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
customComponentManifest: $store.customComponentManifest,
|
customComponentManifest: $store.customComponentManifest,
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { derived } from "svelte/store"
|
||||||
|
import { devToolsStore } from "../devTools.js"
|
||||||
|
import { authStore } from "../auth.js"
|
||||||
|
|
||||||
|
// Derive the current role of the logged-in user
|
||||||
|
export const currentRole = derived(
|
||||||
|
[devToolsStore, authStore],
|
||||||
|
([$devToolsStore, $authStore]) => {
|
||||||
|
return ($devToolsStore.enabled && $devToolsStore.role) || $authStore?.roleId
|
||||||
|
}
|
||||||
|
)
|
|
@ -0,0 +1,2 @@
|
||||||
|
export { isDragging } from "./isDragging.js"
|
||||||
|
export { currentRole } from "./currentRole.js"
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { derived } from "svelte/store"
|
||||||
|
import { dndStore } from "../dnd"
|
||||||
|
import { builderStore } from "../builder.js"
|
||||||
|
|
||||||
|
// Derive whether we are dragging or not
|
||||||
|
export const isDragging = derived(
|
||||||
|
[dndStore, builderStore],
|
||||||
|
([$dndStore, $builderStore]) =>
|
||||||
|
$dndStore.source != null || $builderStore.draggingNewComponent
|
||||||
|
)
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { writable, derived } from "svelte/store"
|
||||||
|
|
||||||
|
const createDndStore = () => {
|
||||||
|
const initialState = {
|
||||||
|
// Info about the dragged component
|
||||||
|
source: null,
|
||||||
|
|
||||||
|
// Info about the target component being hovered over
|
||||||
|
target: null,
|
||||||
|
|
||||||
|
// Info about where the component would be dropped
|
||||||
|
drop: null,
|
||||||
|
}
|
||||||
|
const store = writable(initialState)
|
||||||
|
|
||||||
|
// newComponentType is an optional field to signify we are creating a new
|
||||||
|
// component rather than moving an existing one
|
||||||
|
const startDragging = ({ id, parent, bounds, index, newComponentType }) => {
|
||||||
|
store.set({
|
||||||
|
...initialState,
|
||||||
|
source: { id, parent, bounds, index, newComponentType },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateTarget = ({ id, parent, node, empty, acceptsChildren }) => {
|
||||||
|
store.update(state => {
|
||||||
|
state.target = { id, parent, node, empty, acceptsChildren }
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateDrop = ({ parent, index }) => {
|
||||||
|
store.update(state => {
|
||||||
|
state.drop = { parent, index }
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const reset = () => {
|
||||||
|
store.set(initialState)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe: store.subscribe,
|
||||||
|
actions: {
|
||||||
|
startDragging,
|
||||||
|
updateTarget,
|
||||||
|
updateDrop,
|
||||||
|
reset,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dndStore = createDndStore()
|
||||||
|
export const dndParent = derived(dndStore, $dndStore => $dndStore.drop?.parent)
|
||||||
|
export const dndIndex = derived(dndStore, $dndStore => $dndStore.drop?.index)
|
||||||
|
export const dndBounds = derived(
|
||||||
|
dndStore,
|
||||||
|
$dndStore => $dndStore.source?.bounds
|
||||||
|
)
|
|
@ -1,7 +1,3 @@
|
||||||
import { derived } from "svelte/store"
|
|
||||||
import { devToolsStore } from "./devTools.js"
|
|
||||||
import { authStore } from "./auth.js"
|
|
||||||
|
|
||||||
export { authStore } from "./auth"
|
export { authStore } from "./auth"
|
||||||
export { appStore } from "./app"
|
export { appStore } from "./app"
|
||||||
export { notificationStore } from "./notification"
|
export { notificationStore } from "./notification"
|
||||||
|
@ -19,6 +15,7 @@ export { uploadStore } from "./uploads.js"
|
||||||
export { rowSelectionStore } from "./rowSelection.js"
|
export { rowSelectionStore } from "./rowSelection.js"
|
||||||
export { blockStore } from "./blocks.js"
|
export { blockStore } from "./blocks.js"
|
||||||
export { environmentStore } from "./environment"
|
export { environmentStore } from "./environment"
|
||||||
|
export { dndStore, dndIndex, dndParent, dndBounds } 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
|
||||||
export { createContextStore } from "./context"
|
export { createContextStore } from "./context"
|
||||||
|
@ -26,10 +23,5 @@ export { createContextStore } from "./context"
|
||||||
// Initialises an app by loading screens and routes
|
// Initialises an app by loading screens and routes
|
||||||
export { initialise } from "./initialise"
|
export { initialise } from "./initialise"
|
||||||
|
|
||||||
// Derive the current role of the logged-in user
|
// Derived state
|
||||||
export const currentRole = derived(
|
export * from "./derived"
|
||||||
[devToolsStore, authStore],
|
|
||||||
([$devToolsStore, $authStore]) => {
|
|
||||||
return ($devToolsStore.enabled && $devToolsStore.role) || $authStore?.roleId
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { derived } from "svelte/store"
|
||||||
import { routeStore } from "./routes"
|
import { routeStore } from "./routes"
|
||||||
import { builderStore } from "./builder"
|
import { builderStore } from "./builder"
|
||||||
import { appStore } from "./app"
|
import { appStore } from "./app"
|
||||||
|
import { dndIndex, dndParent } from "./dnd.js"
|
||||||
import { RoleUtils } from "@budibase/frontend-core"
|
import { RoleUtils } from "@budibase/frontend-core"
|
||||||
import { findComponentById, findComponentParent } from "../utils/components.js"
|
import { findComponentById, findComponentParent } from "../utils/components.js"
|
||||||
import { Helpers } from "@budibase/bbui"
|
import { Helpers } from "@budibase/bbui"
|
||||||
|
@ -9,8 +10,8 @@ import { DNDPlaceholderID, DNDPlaceholderType } from "constants"
|
||||||
|
|
||||||
const createScreenStore = () => {
|
const createScreenStore = () => {
|
||||||
const store = derived(
|
const store = derived(
|
||||||
[appStore, routeStore, builderStore],
|
[appStore, routeStore, builderStore, dndParent, dndIndex],
|
||||||
([$appStore, $routeStore, $builderStore]) => {
|
([$appStore, $routeStore, $builderStore, $dndParent, $dndIndex]) => {
|
||||||
let activeLayout, activeScreen
|
let activeLayout, activeScreen
|
||||||
let screens
|
let screens
|
||||||
|
|
||||||
|
@ -46,31 +47,33 @@ const createScreenStore = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert DND placeholder if required
|
// Insert DND placeholder if required
|
||||||
const { dndParent, dndIndex, selectedComponentId } = $builderStore
|
if (activeScreen && $dndParent && $dndIndex != null) {
|
||||||
if (activeScreen && dndParent && dndIndex != null) {
|
// Remove selected component from tree if we are moving an existing
|
||||||
// Remove selected component from tree
|
// component
|
||||||
let selectedParent = findComponentParent(
|
const { selectedComponentId, draggingNewComponent } = $builderStore
|
||||||
activeScreen.props,
|
if (!draggingNewComponent) {
|
||||||
selectedComponentId
|
let selectedParent = findComponentParent(
|
||||||
)
|
activeScreen.props,
|
||||||
selectedParent._children = selectedParent._children?.filter(
|
selectedComponentId
|
||||||
x => x._id !== selectedComponentId
|
)
|
||||||
)
|
if (selectedParent) {
|
||||||
|
selectedParent._children = selectedParent._children?.filter(
|
||||||
|
x => x._id !== selectedComponentId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Insert placeholder
|
// Insert placeholder component
|
||||||
const placeholder = {
|
const placeholder = {
|
||||||
_component: DNDPlaceholderID,
|
_component: DNDPlaceholderID,
|
||||||
_id: DNDPlaceholderType,
|
_id: DNDPlaceholderType,
|
||||||
static: true,
|
static: true,
|
||||||
}
|
}
|
||||||
let parent = findComponentById(activeScreen.props, dndParent)
|
let parent = findComponentById(activeScreen.props, $dndParent)
|
||||||
if (!parent._children?.length) {
|
if (!parent._children?.length) {
|
||||||
parent._children = [placeholder]
|
parent._children = [placeholder]
|
||||||
} else {
|
} else {
|
||||||
parent._children = parent._children.filter(
|
parent._children.splice($dndIndex, 0, placeholder)
|
||||||
x => x._id !== selectedComponentId
|
|
||||||
)
|
|
||||||
parent._children.splice(dndIndex, 0, placeholder)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue