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