Make DND feel much smoother by persisting the end position of drops, and more performance by properly memoizing some state values
This commit is contained in:
parent
f1714ab2a5
commit
794db1a7db
|
@ -57,8 +57,10 @@
|
|||
}
|
||||
|
||||
// Reset state
|
||||
if (!$dndStore.dropped) {
|
||||
dndStore.actions.reset()
|
||||
}
|
||||
}
|
||||
|
||||
// Callback when initially starting a drag on a draggable component
|
||||
const onDragStart = e => {
|
||||
|
@ -251,7 +253,7 @@
|
|||
}
|
||||
|
||||
// Callback when dropping a drag on top of some component
|
||||
const onDrop = e => {
|
||||
const onDrop = () => {
|
||||
if (!source || !drop?.parent || drop?.index == null) {
|
||||
return
|
||||
}
|
||||
|
@ -299,6 +301,7 @@
|
|||
}
|
||||
|
||||
if (legacyDropTarget && legacyDropMode) {
|
||||
dndStore.actions.markDropped()
|
||||
builderStore.actions.moveComponent(
|
||||
source.id,
|
||||
legacyDropTarget,
|
||||
|
|
|
@ -24,10 +24,6 @@ loadSpectrumIcons()
|
|||
let app
|
||||
|
||||
const loadBudibase = async () => {
|
||||
if (get(builderStore).clearGridNextLoad) {
|
||||
builderStore.actions.setDragging(false)
|
||||
}
|
||||
|
||||
// Update builder store with any builder flags
|
||||
builderStore.set({
|
||||
...get(builderStore),
|
||||
|
@ -45,6 +41,11 @@ const loadBudibase = async () => {
|
|||
location: window["##BUDIBASE_LOCATION##"],
|
||||
})
|
||||
|
||||
// Reset DND state if we completed a successful drop
|
||||
if (get(dndStore).dropped) {
|
||||
dndStore.actions.reset()
|
||||
}
|
||||
|
||||
// Set app ID - this window flag is set by both the preview and the real
|
||||
// server rendered app HTML
|
||||
appStore.actions.setAppId(window["##BUDIBASE_APP_ID##"])
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { writable, derived } from "svelte/store"
|
||||
import { computed } from "../utils/computed.js"
|
||||
|
||||
const createDndStore = () => {
|
||||
const initialState = {
|
||||
|
@ -10,6 +11,9 @@ const createDndStore = () => {
|
|||
|
||||
// Info about where the component would be dropped
|
||||
drop: null,
|
||||
|
||||
// Whether the current drop has been completed successfully
|
||||
dropped: false,
|
||||
}
|
||||
const store = writable(initialState)
|
||||
|
||||
|
@ -59,6 +63,13 @@ const createDndStore = () => {
|
|||
store.set(initialState)
|
||||
}
|
||||
|
||||
const markDropped = () => {
|
||||
store.update(state => {
|
||||
state.dropped = true
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe: store.subscribe,
|
||||
actions: {
|
||||
|
@ -67,6 +78,7 @@ const createDndStore = () => {
|
|||
updateTarget,
|
||||
updateDrop,
|
||||
reset,
|
||||
markDropped,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -77,14 +89,12 @@ export const dndStore = createDndStore()
|
|||
// performance by deriving any state that needs to be externally observed.
|
||||
// By doing this and using primitives, we can avoid invalidating other stores
|
||||
// or components which depend on DND state unless values actually change.
|
||||
export const dndParent = derived(dndStore, $dndStore => $dndStore.drop?.parent)
|
||||
export const dndIndex = derived(dndStore, $dndStore => $dndStore.drop?.index)
|
||||
export const dndBounds = derived(
|
||||
export const dndParent = computed(dndStore, x => x.drop?.parent)
|
||||
export const dndIndex = computed(dndStore, x => x.drop?.index)
|
||||
export const dndDropped = computed(dndStore, x => x.dropped)
|
||||
export const dndBounds = computed(dndStore, x => x.source?.bounds)
|
||||
export const dndIsDragging = computed(dndStore, x => !!x.source)
|
||||
export const dndIsNewComponent = computed(
|
||||
dndStore,
|
||||
$dndStore => $dndStore.source?.bounds
|
||||
x => x.source?.newComponentType != null
|
||||
)
|
||||
export const dndIsNewComponent = derived(
|
||||
dndStore,
|
||||
$dndStore => $dndStore.source?.newComponentType != null
|
||||
)
|
||||
export const dndIsDragging = derived(dndStore, $dndStore => !!$dndStore.source)
|
||||
|
|
|
@ -22,6 +22,7 @@ export {
|
|||
dndBounds,
|
||||
dndIsNewComponent,
|
||||
dndIsDragging,
|
||||
dndDropped,
|
||||
} from "./dnd"
|
||||
|
||||
// Context stores are layered and duplicated, so it is not a singleton
|
||||
|
|
|
@ -2,7 +2,13 @@ import { derived } from "svelte/store"
|
|||
import { routeStore } from "./routes"
|
||||
import { builderStore } from "./builder"
|
||||
import { appStore } from "./app"
|
||||
import { dndIndex, dndParent, dndIsNewComponent, dndBounds } from "./dnd.js"
|
||||
import {
|
||||
dndIndex,
|
||||
dndParent,
|
||||
dndIsNewComponent,
|
||||
dndBounds,
|
||||
dndDropped,
|
||||
} from "./dnd.js"
|
||||
import { RoleUtils } from "@budibase/frontend-core"
|
||||
import { findComponentById, findComponentParent } from "../utils/components.js"
|
||||
import { Helpers } from "@budibase/bbui"
|
||||
|
@ -18,6 +24,7 @@ const createScreenStore = () => {
|
|||
dndIndex,
|
||||
dndIsNewComponent,
|
||||
dndBounds,
|
||||
dndDropped,
|
||||
],
|
||||
([
|
||||
$appStore,
|
||||
|
@ -27,6 +34,7 @@ const createScreenStore = () => {
|
|||
$dndIndex,
|
||||
$dndIsNewComponent,
|
||||
$dndBounds,
|
||||
$dndDropped,
|
||||
]) => {
|
||||
let activeLayout, activeScreen
|
||||
let screens
|
||||
|
@ -64,39 +72,54 @@ const createScreenStore = () => {
|
|||
|
||||
// Insert DND placeholder if required
|
||||
if (activeScreen && $dndParent && $dndIndex != null) {
|
||||
// Remove selected component from tree if we are moving an existing
|
||||
// component
|
||||
const { selectedComponentId } = $builderStore
|
||||
if (!$dndIsNewComponent) {
|
||||
|
||||
// Extract and save the selected component as we need a reference to it
|
||||
// later, and we may be removing it
|
||||
let selectedParent = findComponentParent(
|
||||
activeScreen.props,
|
||||
selectedComponentId
|
||||
)
|
||||
if (selectedParent) {
|
||||
const selectedComponent = selectedParent?._children?.find(
|
||||
x => x._id === selectedComponentId
|
||||
)
|
||||
|
||||
// Remove selected component from tree if we are moving an existing
|
||||
// component
|
||||
if (!$dndIsNewComponent && selectedParent) {
|
||||
selectedParent._children = selectedParent._children?.filter(
|
||||
x => x._id !== selectedComponentId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Insert placeholder component
|
||||
const placeholder = {
|
||||
// Insert placeholder component if dragging, or artificially insert
|
||||
// the dropped component in the new location if the drop completed
|
||||
let componentToInsert
|
||||
if ($dndDropped && !$dndIsNewComponent) {
|
||||
componentToInsert = selectedComponent
|
||||
} else {
|
||||
componentToInsert = {
|
||||
_component: "@budibase/standard-components/container",
|
||||
_id: DNDPlaceholderID,
|
||||
_styles: {
|
||||
normal: {
|
||||
width: `${$dndBounds?.width || 666}px`,
|
||||
height: `${$dndBounds?.height || 666}px`,
|
||||
width: `${$dndBounds?.width || 400}px`,
|
||||
height: `${$dndBounds?.height || 200}px`,
|
||||
opacity: 0,
|
||||
},
|
||||
},
|
||||
static: true,
|
||||
}
|
||||
}
|
||||
if (componentToInsert) {
|
||||
let parent = findComponentById(activeScreen.props, $dndParent)
|
||||
if (parent) {
|
||||
if (!parent._children?.length) {
|
||||
parent._children = [placeholder]
|
||||
parent._children = [componentToInsert]
|
||||
} else {
|
||||
parent._children.splice($dndIndex, 0, placeholder)
|
||||
parent._children.splice($dndIndex, 0, componentToInsert)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
import { writable } from "svelte/store"
|
||||
|
||||
const getKey = value => {
|
||||
if (value == null || typeof value !== "object") {
|
||||
return value
|
||||
} else {
|
||||
return JSON.stringify(value)
|
||||
}
|
||||
}
|
||||
|
||||
export const computed = (store, getValue) => {
|
||||
const initialValue = getValue(store)
|
||||
const computedStore = writable(initialValue)
|
||||
let lastKey = getKey(initialValue)
|
||||
|
||||
store.subscribe(state => {
|
||||
const value = getValue(state)
|
||||
const key = getKey(value)
|
||||
if (key !== lastKey) {
|
||||
lastKey = key
|
||||
computedStore.set(value)
|
||||
}
|
||||
})
|
||||
|
||||
return computedStore
|
||||
}
|
Loading…
Reference in New Issue