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