Add ability to drag new components into the preview

This commit is contained in:
Andrew Kingston 2022-10-14 13:37:14 +01:00
parent 2c8dd09a56
commit d997afffc0
17 changed files with 318 additions and 174 deletions

View File

@ -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,7 +461,20 @@ export const getFrontendStore = () => {
return return
} }
// Patch selected screen // Insert in position if specified
if (parent && index != null) {
await store.actions.screens.patch(screen => {
let parentComponent = findComponent(screen.props, parent)
if (!parentComponent._children?.length) {
parentComponent._children = [componentInstance]
} else {
parentComponent._children.splice(index, 0, componentInstance)
}
})
}
// Otherwise we work out where this component should be inserted
else {
await store.actions.screens.patch(screen => { await store.actions.screens.patch(screen => {
// Find the selected component // Find the selected component
const currentComponent = findComponent( const currentComponent = findComponent(
@ -503,6 +516,7 @@ export const getFrontendStore = () => {
} }
parentComponent._children.push(componentInstance) 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

View File

@ -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}`)
} }

View File

@ -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 ===

View File

@ -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
}
// Check if we're adding a new component rather than moving one
if (source.newComponentType) {
builderStore.actions.dropNewComponent(
source.newComponentType,
drop.parent,
drop.index
)
}
// Convert parent + index into target + mode // Convert parent + index into target + mode
if (sourceInfo && dropInfo?.parent && dropInfo.index != null) { let legacyDropTarget, legacyDropMode
const parent = findComponentById( const parent = findComponentById(
get(componentStore).currentAsset?.props, get(componentStore).currentAsset?.props,
dropInfo.parent drop.parent
) )
if (!parent) { if (!parent) {
return return
} }
// Do nothing if we didn't change the location // Do nothing if we didn't change the location
if ( if (source.parent === drop.parent && source.index === drop.index) {
sourceInfo.parent === dropInfo.parent &&
sourceInfo.index === dropInfo.index
) {
return return
} }
// Filter out source component and placeholder from consideration // Filter out source component and placeholder from consideration
const children = parent._children?.filter( const children = parent._children?.filter(
x => x._id !== "placeholder" && x._id !== sourceInfo.id x => x._id !== "placeholder" && x._id !== source.id
) )
// Use inside if no existing children // Use inside if no existing children
if (!children?.length) { if (!children?.length) {
target = parent._id legacyDropTarget = parent._id
mode = "inside" legacyDropMode = "inside"
} else if (dropInfo.index === 0) { } else if (drop.index === 0) {
target = children[0]?._id legacyDropTarget = children[0]?._id
mode = "above" legacyDropMode = "above"
} else { } else {
target = children[dropInfo.index - 1]?._id legacyDropTarget = children[drop.index - 1]?._id
mode = "below" legacyDropMode = "below"
}
} }
if (target && mode) { if (legacyDropTarget && legacyDropMode) {
builderStore.actions.moveComponent(sourceInfo.id, target, mode) 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}

View File

@ -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) {

View File

@ -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}

View File

@ -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

View File

@ -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 => {

View File

@ -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)
}
} }
} }

View File

@ -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,

View File

@ -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,

View File

@ -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
}
)

View File

@ -0,0 +1,2 @@
export { isDragging } from "./isDragging.js"
export { currentRole } from "./currentRole.js"

View File

@ -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
)

View File

@ -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
)

View File

@ -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
}
)

View File

@ -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
const { selectedComponentId, draggingNewComponent } = $builderStore
if (!draggingNewComponent) {
let selectedParent = findComponentParent( let selectedParent = findComponentParent(
activeScreen.props, activeScreen.props,
selectedComponentId selectedComponentId
) )
if (selectedParent) {
selectedParent._children = selectedParent._children?.filter( selectedParent._children = selectedParent._children?.filter(
x => x._id !== selectedComponentId 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)
} }
} }