forked from tsai/budibase
8 changed files with 217 additions and 1105 deletions
@ -0,0 +1,9 @@ |
|||||
|
<script> |
||||
|
</script> |
||||
|
|
||||
|
{#if $dndStore.targetParent?._id === component._id && $dndStore.dropPosition !== DropPosition.INSIDE} |
||||
|
<div |
||||
|
class="inside drop-item" |
||||
|
style="--indicatorX: {indicatorX}px; --indicatorY:{indicatorY}px;" |
||||
|
/> |
||||
|
{/if} |
||||
@ -0,0 +1,47 @@ |
|||||
|
<script> |
||||
|
import { level } from "./ComponentTree.svelte" |
||||
|
|
||||
|
export let componentId |
||||
|
export let dndStore |
||||
|
|
||||
|
const indicatorX = (level + 2) * 14 |
||||
|
let indicatorY = 0 |
||||
|
</script> |
||||
|
|
||||
|
{#if $dndStore.dragging && $dndStore.valid} |
||||
|
{#if $dndStore?.target?._id === componentId} |
||||
|
<div |
||||
|
class:above={$dndStore.dropPosition === DropPosition.ABOVE} |
||||
|
class:below={$dndStore.dropPosition === DropPosition.BELOW} |
||||
|
class:inside={$dndStore.dropPosition === DropPosition.INSIDE} |
||||
|
class="drop-item" |
||||
|
style="--indicatorX: {indicatorX}px; --indicatorY:{indicatorY}px;" |
||||
|
/> |
||||
|
{/if} |
||||
|
{/if} |
||||
|
] |
||||
|
|
||||
|
<style> |
||||
|
.drop-item { |
||||
|
height: 2px; |
||||
|
background: var(--spectrum-global-color-static-green-500); |
||||
|
z-index: 999; |
||||
|
position: absolute; |
||||
|
left: var(--indicatorX); |
||||
|
width: calc(100% - var(--indicatorX)); |
||||
|
border-radius: 4px; |
||||
|
pointer-events: none; |
||||
|
} |
||||
|
.drop-item.above { |
||||
|
} |
||||
|
.drop-item.below { |
||||
|
margin-top: 32px; |
||||
|
} |
||||
|
.drop-item.inside { |
||||
|
background: transparent; |
||||
|
border: 2px solid var(--spectrum-global-color-static-green-500); |
||||
|
height: 29px; |
||||
|
pointer-events: none; |
||||
|
width: calc(100% - var(--indicatorX) - 4px); |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,127 @@ |
|||||
|
import { writable, get } from "svelte/store" |
||||
|
import { store as frontendStore, selectedScreen } from "builderStore" |
||||
|
import { |
||||
|
findComponentParent, |
||||
|
findComponentPath, |
||||
|
} from "builderStore/componentUtils" |
||||
|
|
||||
|
export const DropPosition = { |
||||
|
ABOVE: "above", |
||||
|
BELOW: "below", |
||||
|
INSIDE: "inside", |
||||
|
} |
||||
|
|
||||
|
const initialState = { |
||||
|
source: null, |
||||
|
target: null, |
||||
|
targetParent: null, |
||||
|
dropPosition: null, |
||||
|
dragging: false, |
||||
|
valid: false, |
||||
|
} |
||||
|
|
||||
|
export default function () { |
||||
|
const store = writable(initialState) |
||||
|
const actions = { |
||||
|
dragstart: component => { |
||||
|
if (!component) { |
||||
|
return |
||||
|
} |
||||
|
store.set({ |
||||
|
source: component, |
||||
|
target: null, |
||||
|
dropPosition: null, |
||||
|
dragging: true, |
||||
|
valid: false, |
||||
|
}) |
||||
|
}, |
||||
|
dragover: ({ component, mousePosition }) => { |
||||
|
const definition = frontendStore.actions.components.getDefinition( |
||||
|
component._component |
||||
|
) |
||||
|
const canHaveChildren = definition?.hasChildren |
||||
|
const hasChildren = component._children?.length > 0 |
||||
|
|
||||
|
let dropPosition |
||||
|
let target |
||||
|
let targetParent |
||||
|
|
||||
|
// If the component has children, it cannot be dropped below
|
||||
|
if (hasChildren) { |
||||
|
if (mousePosition <= 0.33) { |
||||
|
dropPosition = DropPosition.ABOVE |
||||
|
} else { |
||||
|
dropPosition = DropPosition.INSIDE |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// If it can have children then it can be any position
|
||||
|
else if (canHaveChildren) { |
||||
|
if (mousePosition <= 0.33) { |
||||
|
dropPosition = DropPosition.ABOVE |
||||
|
} else if (mousePosition >= 0.66) { |
||||
|
dropPosition = DropPosition.BELOW |
||||
|
} else { |
||||
|
dropPosition = DropPosition.INSIDE |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Otherwise drop either above or below
|
||||
|
else { |
||||
|
dropPosition = |
||||
|
mousePosition > 0.5 ? DropPosition.BELOW : DropPosition.ABOVE |
||||
|
} |
||||
|
|
||||
|
// If hovering over a component with children and attempting to drop
|
||||
|
// below, we need to change this to be above the first child instead
|
||||
|
if (dropPosition === DropPosition.BELOW && hasChildren) { |
||||
|
target = component._children[0] |
||||
|
dropPosition = DropPosition.ABOVE |
||||
|
} else { |
||||
|
target = component |
||||
|
} |
||||
|
|
||||
|
// Find the parent of the target component
|
||||
|
if (target) { |
||||
|
targetParent = findComponentParent( |
||||
|
get(selectedScreen).props, |
||||
|
target._id |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
store.update(state => { |
||||
|
return { |
||||
|
...state, |
||||
|
dropPosition, |
||||
|
target, |
||||
|
targetParent, |
||||
|
|
||||
|
/// Mark as invalid if the target is a child of the source
|
||||
|
valid: findComponentPath(state.source, target._id)?.length === 0, |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
reset: () => { |
||||
|
store.set(initialState) |
||||
|
}, |
||||
|
drop: async () => { |
||||
|
const state = get(store) |
||||
|
if (!state.valid || !state.source || !state.target) { |
||||
|
return |
||||
|
} |
||||
|
actions.reset() |
||||
|
|
||||
|
// Cut and paste the component
|
||||
|
frontendStore.actions.components.copy(state.source, true, false) |
||||
|
await frontendStore.actions.components.paste( |
||||
|
state.target, |
||||
|
state.dropPosition |
||||
|
) |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
subscribe: store.subscribe, |
||||
|
actions, |
||||
|
} |
||||
|
} |
||||
@ -1,90 +0,0 @@ |
|||||
import { writable, get } from "svelte/store" |
|
||||
import { store as frontendStore } from "builderStore" |
|
||||
import { findComponentPath } from "builderStore/componentUtils" |
|
||||
|
|
||||
export const DropEffect = { |
|
||||
MOVE: "move", |
|
||||
COPY: "copy", |
|
||||
} |
|
||||
|
|
||||
export const DropPosition = { |
|
||||
ABOVE: "above", |
|
||||
BELOW: "below", |
|
||||
INSIDE: "inside", |
|
||||
} |
|
||||
|
|
||||
export default function () { |
|
||||
const store = writable({}) |
|
||||
|
|
||||
store.actions = { |
|
||||
dragstart: component => { |
|
||||
store.update(state => { |
|
||||
state.dragged = component |
|
||||
return state |
|
||||
}) |
|
||||
}, |
|
||||
dragover: ({ component, canHaveChildren, hasChildren, mousePosition }) => { |
|
||||
store.update(state => { |
|
||||
if (canHaveChildren) { |
|
||||
if (mousePosition <= 0.33) { |
|
||||
state.dropPosition = DropPosition.ABOVE |
|
||||
} else if (mousePosition >= 0.66) { |
|
||||
state.dropPosition = DropPosition.BELOW |
|
||||
} else { |
|
||||
state.dropPosition = DropPosition.INSIDE |
|
||||
} |
|
||||
} else { |
|
||||
state.dropPosition = |
|
||||
mousePosition > 0.5 ? DropPosition.BELOW : DropPosition.ABOVE |
|
||||
} |
|
||||
|
|
||||
// If hovering over a component with children and attempting to drop
|
|
||||
// below, we need to change this to be above the first child instead
|
|
||||
if (state.dropPosition === DropPosition.BELOW && hasChildren) { |
|
||||
state.targetComponent = component._children[0] |
|
||||
state.dropPosition = DropPosition.ABOVE |
|
||||
} else { |
|
||||
state.targetComponent = component |
|
||||
} |
|
||||
|
|
||||
return state |
|
||||
}) |
|
||||
}, |
|
||||
reset: () => { |
|
||||
store.update(state => { |
|
||||
state.dropPosition = "" |
|
||||
state.targetComponent = null |
|
||||
state.dragged = null |
|
||||
return state |
|
||||
}) |
|
||||
}, |
|
||||
drop: async () => { |
|
||||
const state = get(store) |
|
||||
|
|
||||
// Stop if the target and source are the same
|
|
||||
if (state.targetComponent === state.dragged) { |
|
||||
return |
|
||||
} |
|
||||
// Stop if the target or source are null
|
|
||||
if (!state.targetComponent || !state.dragged) { |
|
||||
return |
|
||||
} |
|
||||
// Stop if the target is a child of source
|
|
||||
const path = findComponentPath(state.dragged, state.targetComponent._id) |
|
||||
const ids = path.map(component => component._id) |
|
||||
if (ids.includes(state.targetComponent._id)) { |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
// Cut and paste the component
|
|
||||
frontendStore.actions.components.copy(state.dragged, true, false) |
|
||||
await frontendStore.actions.components.paste( |
|
||||
state.targetComponent, |
|
||||
state.dropPosition |
|
||||
) |
|
||||
store.actions.reset() |
|
||||
}, |
|
||||
} |
|
||||
|
|
||||
return store |
|
||||
} |
|
||||
File diff suppressed because it is too large
Loading…
Reference in new issue