Use new technique for DND selection indicators
This commit is contained in:
parent
0471e113e3
commit
0ad0ded2e2
|
@ -44,7 +44,7 @@
|
|||
import MaintenanceScreen from "components/MaintenanceScreen.svelte"
|
||||
import SnippetsProvider from "./context/SnippetsProvider.svelte"
|
||||
import EmbedProvider from "./context/EmbedProvider.svelte"
|
||||
import GridNewComponentDNDHandler from "components/preview/GridNewComponentDNDHandler.svelte"
|
||||
import DNDSelectionIndicators from "./preview/DNDSelectionIndicators.svelte"
|
||||
|
||||
// Provide contexts
|
||||
setContext("sdk", SDK)
|
||||
|
@ -267,7 +267,7 @@
|
|||
{#if $builderStore.inBuilder}
|
||||
<DNDHandler />
|
||||
<GridDNDHandler />
|
||||
<GridNewComponentDNDHandler />
|
||||
<DNDSelectionIndicators />
|
||||
{/if}
|
||||
</div>
|
||||
</SnippetsProvider>
|
||||
|
|
|
@ -120,7 +120,7 @@
|
|||
$: children = instance._children || []
|
||||
$: id = instance._id
|
||||
$: name = isRoot ? "Screen" : instance._instanceName
|
||||
$: icon = definition?.icon
|
||||
$: icon = instance._icon || definition?.icon
|
||||
|
||||
// Determine if the component is selected or is part of the critical path
|
||||
// leading to the selected component
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
screenStore,
|
||||
dndStore,
|
||||
dndParent,
|
||||
dndSource,
|
||||
dndIsDragging,
|
||||
isGridScreen,
|
||||
} from "stores"
|
||||
import DNDPlaceholderOverlay from "./DNDPlaceholderOverlay.svelte"
|
||||
import { Utils } from "@budibase/frontend-core"
|
||||
import { findComponentById } from "@/utils/components.js"
|
||||
import { isGridEvent } from "@/utils/grid"
|
||||
|
@ -92,6 +92,8 @@
|
|||
bounds: component.children[0].getBoundingClientRect(),
|
||||
parent: parentId,
|
||||
index,
|
||||
name: component.dataset.name,
|
||||
icon: component.dataset.icon,
|
||||
})
|
||||
builderStore.actions.selectComponent(id)
|
||||
|
||||
|
@ -258,10 +260,10 @@
|
|||
}
|
||||
|
||||
// Check if we're adding a new component rather than moving one
|
||||
if (source.newComponentType) {
|
||||
if (source.isNew) {
|
||||
dropping = true
|
||||
builderStore.actions.dropNewComponent(
|
||||
source.newComponentType,
|
||||
source.type,
|
||||
drop.parent,
|
||||
drop.index,
|
||||
$dndStore.meta.newComponentProps
|
||||
|
@ -335,16 +337,3 @@
|
|||
document.removeEventListener("drop", onDrop, false)
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if !$isGridScreen}
|
||||
<IndicatorSet
|
||||
componentId={$dndParent}
|
||||
color="var(--spectrum-global-color-static-green-500)"
|
||||
zIndex={920}
|
||||
prefix="Inside"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if $dndIsDragging}
|
||||
<DNDPlaceholderOverlay />
|
||||
{/if}
|
||||
|
|
|
@ -1,96 +1,108 @@
|
|||
<script>
|
||||
import { onMount, tick } from "svelte"
|
||||
import { Utils } from "@budibase/frontend-core"
|
||||
import { componentStore, isGridScreen } from "@/stores"
|
||||
import { DNDPlaceholderID } from "@/constants"
|
||||
<!--<script>-->
|
||||
<!-- import { onMount, tick } from "svelte"-->
|
||||
<!-- import { Utils } from "@budibase/frontend-core"-->
|
||||
<!-- import { componentStore, dndNewComponentType, isGridScreen } from "@/stores"-->
|
||||
<!-- import { DNDPlaceholderID } from "@/constants"-->
|
||||
<!-- import IndicatorSet from "components/preview/IndicatorSet.svelte"-->
|
||||
|
||||
let left, top, height, width
|
||||
let observing = false
|
||||
let hasGridStyles = false
|
||||
<!-- let left, top, height, width-->
|
||||
<!-- let observing = false-->
|
||||
<!-- let hasGridStyles = false-->
|
||||
|
||||
// On grid screens, we need to wait for grid styles to be properly set on
|
||||
// the hidden placeholder component before rendering this overlay
|
||||
$: waitingForGrid = $isGridScreen && !hasGridStyles
|
||||
$: instance = componentStore.actions.getComponentInstance(DNDPlaceholderID)
|
||||
$: state = $instance?.state
|
||||
$: styles = $state?.styles?.normal || {}
|
||||
$: {
|
||||
if ($isGridScreen && !hasGridStyles) {
|
||||
checkGridStyles(styles)
|
||||
}
|
||||
}
|
||||
<!-- // On grid screens, we need to wait for grid styles to be properly set on-->
|
||||
<!-- // the hidden placeholder component before rendering this overlay-->
|
||||
<!-- $: waitingForGrid = $isGridScreen && !hasGridStyles-->
|
||||
<!-- $: instance = componentStore.actions.getComponentInstance(DNDPlaceholderID)-->
|
||||
<!-- $: state = $instance?.state-->
|
||||
<!-- $: styles = $state?.styles?.normal || {}-->
|
||||
<!-- $: {-->
|
||||
<!-- if ($isGridScreen && !hasGridStyles) {-->
|
||||
<!-- checkGridStyles(styles)-->
|
||||
<!-- }-->
|
||||
<!-- }-->
|
||||
|
||||
// Wait for grid styles to be set, then tick and await a position update
|
||||
// before finally signalling we're allowed to render
|
||||
const checkGridStyles = async styles => {
|
||||
const hasStyles = Object.keys(styles).some(key => key.startsWith("--grid"))
|
||||
if (hasStyles) {
|
||||
await tick()
|
||||
updatePosition()
|
||||
hasGridStyles = true
|
||||
}
|
||||
}
|
||||
<!-- // We pull the component name from the definition-->
|
||||
<!-- $: definition =-->
|
||||
<!-- componentStore.actions.getComponentDefinition($dndNewComponentType)-->
|
||||
|
||||
// Observe style changes in the placeholder DOM node and use this to trigger
|
||||
// a redraw of our overlay
|
||||
const observer = new MutationObserver(mutations => {
|
||||
if (mutations.some(mutation => mutation.attributeName === "style")) {
|
||||
debouncedUpdate()
|
||||
}
|
||||
})
|
||||
<!-- // Wait for grid styles to be set, then tick and await a position update-->
|
||||
<!-- // before finally signalling we're allowed to render-->
|
||||
<!-- const checkGridStyles = async styles => {-->
|
||||
<!-- const hasStyles = Object.keys(styles).some(key => key.startsWith("--grid"))-->
|
||||
<!-- if (hasStyles) {-->
|
||||
<!-- await tick()-->
|
||||
<!-- updatePosition()-->
|
||||
<!-- hasGridStyles = true-->
|
||||
<!-- }-->
|
||||
<!-- }-->
|
||||
|
||||
const updatePosition = () => {
|
||||
const wrapperNode = document.getElementsByClassName(DNDPlaceholderID)[0]
|
||||
let domNode = wrapperNode
|
||||
const insideGrid = wrapperNode?.dataset.insideGrid === "true"
|
||||
if (!insideGrid) {
|
||||
domNode = document.getElementsByClassName(`${DNDPlaceholderID}-dom`)[0]
|
||||
}
|
||||
if (!domNode) {
|
||||
height = 0
|
||||
width = 0
|
||||
} else {
|
||||
const bounds = domNode.getBoundingClientRect()
|
||||
left = bounds.left
|
||||
top = bounds.top
|
||||
height = bounds.height
|
||||
width = bounds.width
|
||||
}
|
||||
<!-- // Observe style changes in the placeholder DOM node and use this to trigger-->
|
||||
<!-- // a redraw of our overlay-->
|
||||
<!-- const observer = new MutationObserver(mutations => {-->
|
||||
<!-- if (mutations.some(mutation => mutation.attributeName === "style")) {-->
|
||||
<!-- debouncedUpdate()-->
|
||||
<!-- }-->
|
||||
<!-- })-->
|
||||
|
||||
// Initialise observer if not already done
|
||||
if (!observing && wrapperNode) {
|
||||
observing = true
|
||||
observer.observe(wrapperNode, { attributes: true })
|
||||
}
|
||||
}
|
||||
const debouncedUpdate = Utils.domDebounce(updatePosition)
|
||||
<!-- const updatePosition = () => {-->
|
||||
<!-- const wrapperNode = document.getElementsByClassName(DNDPlaceholderID)[0]-->
|
||||
<!-- let domNode = wrapperNode-->
|
||||
<!-- const insideGrid = wrapperNode?.dataset.insideGrid === "true"-->
|
||||
<!-- if (!insideGrid) {-->
|
||||
<!-- domNode = document.getElementsByClassName(`${DNDPlaceholderID}-dom`)[0]-->
|
||||
<!-- }-->
|
||||
<!-- if (!domNode) {-->
|
||||
<!-- height = 0-->
|
||||
<!-- width = 0-->
|
||||
<!-- } else {-->
|
||||
<!-- const bounds = domNode.getBoundingClientRect()-->
|
||||
<!-- left = bounds.left-->
|
||||
<!-- top = bounds.top-->
|
||||
<!-- height = bounds.height-->
|
||||
<!-- width = bounds.width-->
|
||||
<!-- }-->
|
||||
|
||||
onMount(() => {
|
||||
const interval = setInterval(debouncedUpdate, 100)
|
||||
return () => {
|
||||
observer.disconnect()
|
||||
clearInterval(interval)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!-- // Initialise observer if not already done-->
|
||||
<!-- if (!observing && wrapperNode) {-->
|
||||
<!-- observing = true-->
|
||||
<!-- observer.observe(wrapperNode, { attributes: true })-->
|
||||
<!-- }-->
|
||||
<!-- }-->
|
||||
<!-- const debouncedUpdate = Utils.domDebounce(updatePosition)-->
|
||||
|
||||
{#if left != null && top != null && width && height && !waitingForGrid}
|
||||
<div
|
||||
class="overlay"
|
||||
class:animate={!$isGridScreen}
|
||||
style="left: {left}px; top: {top}px; width: {width}px; height: {height}px;"
|
||||
/>
|
||||
{/if}
|
||||
<!-- onMount(() => {-->
|
||||
<!-- const interval = setInterval(debouncedUpdate, 100)-->
|
||||
<!-- return () => {-->
|
||||
<!-- observer.disconnect()-->
|
||||
<!-- clearInterval(interval)-->
|
||||
<!-- }-->
|
||||
<!-- })-->
|
||||
<!--</script>-->
|
||||
|
||||
<style>
|
||||
.overlay {
|
||||
position: fixed;
|
||||
z-index: 800;
|
||||
background: hsl(160, 64%, 90%);
|
||||
border-radius: 4px;
|
||||
border: 2px solid var(--spectrum-global-color-static-green-500);
|
||||
}
|
||||
.overlay.animate {
|
||||
transition: all 130ms ease-out;
|
||||
}
|
||||
</style>
|
||||
<!--<!–{#if left != null && top != null && width && height && !waitingForGrid}–>-->
|
||||
<!--<!– <div–>-->
|
||||
<!--<!– class="overlay"–>-->
|
||||
<!--<!– class:animate={!$isGridScreen}–>-->
|
||||
<!--<!– style="left:{left}px; top:{top}px; width:{width}px; height:{height}px;"–>-->
|
||||
<!--<!– >–>-->
|
||||
<!--<!– {definition?.name || ""}–>-->
|
||||
<!--<!– </div>–>-->
|
||||
<!--<!– <IndicatorSet componentId={DNDPlaceholderID} color="red" />–>-->
|
||||
|
||||
<!--<!–{/if}–>-->
|
||||
<!--<style>-->
|
||||
<!-- .overlay {-->
|
||||
<!-- position: fixed;-->
|
||||
<!-- z-index: 800;-->
|
||||
<!-- background: hsl(160, 64%, 90%);-->
|
||||
<!-- border-radius: 4px;-->
|
||||
<!-- border: 2px solid var(--spectrum-global-color-static-green-500);-->
|
||||
<!-- display: grid;-->
|
||||
<!-- place-items: center;-->
|
||||
<!-- color: hsl(160, 64%, 40%);-->
|
||||
<!-- font-size: 14px;-->
|
||||
<!-- }-->
|
||||
<!-- .overlay.animate {-->
|
||||
<!-- transition: all 130ms ease-out;-->
|
||||
<!-- }-->
|
||||
<!--</style>-->
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
<script lang="ts">
|
||||
import { isGridScreen, dndParent, dndSource } from "@/stores"
|
||||
import { DNDPlaceholderID } from "@/constants"
|
||||
import IndicatorSet from "./IndicatorSet.svelte"
|
||||
</script>
|
||||
|
||||
{#if !$isGridScreen}
|
||||
<IndicatorSet
|
||||
componentId={$dndParent}
|
||||
color="var(--spectrum-global-color-static-green-400)"
|
||||
zIndex={920}
|
||||
prefix="Inside"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if $dndIsDragging}
|
||||
<IndicatorSet
|
||||
componentId={DNDPlaceholderID}
|
||||
color="var(--spectrum-global-color-static-green-500)"
|
||||
zIndex={930}
|
||||
allowResizeAnchors={false}
|
||||
background="hsl(160, 64%, 90%)"
|
||||
animate={!$isGridScreen}
|
||||
text={$dndSource?.name}
|
||||
icon={$dndSource?.icon}
|
||||
/>
|
||||
{/if}
|
|
@ -4,8 +4,8 @@
|
|||
builderStore,
|
||||
componentStore,
|
||||
dndIsDragging,
|
||||
dndIsNewComponent,
|
||||
dndStore,
|
||||
dndSource,
|
||||
isGridScreen,
|
||||
} from "stores"
|
||||
import { Utils, memo } from "@budibase/frontend-core"
|
||||
|
@ -53,7 +53,7 @@
|
|||
// If dragging a new component on to a grid screen, tick to allow the
|
||||
// real component to render in the new position before updating the DND
|
||||
// store, preventing the green DND overlay from being out of position
|
||||
if ($dndIsNewComponent && styles) {
|
||||
if ($dndSource?.isNew && styles) {
|
||||
dndStore.actions.updateNewComponentProps({
|
||||
_styles: {
|
||||
normal: styles,
|
||||
|
@ -222,7 +222,7 @@
|
|||
const onDragOver = e => {
|
||||
if (!dragInfo) {
|
||||
// Check if we're dragging a new component
|
||||
if ($dndIsDragging && $dndIsNewComponent && $isGridScreen) {
|
||||
if ($dndIsDragging && $dndSource?.isNew && $isGridScreen) {
|
||||
startDraggingPlaceholder()
|
||||
}
|
||||
return
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
export let line = false
|
||||
export let alignRight = false
|
||||
export let showResizeAnchors = false
|
||||
export let background = null
|
||||
export let animate = false
|
||||
|
||||
const AnchorSides = [
|
||||
"right",
|
||||
|
@ -33,10 +35,12 @@
|
|||
class="indicator"
|
||||
class:flipped
|
||||
class:line
|
||||
style="top: {top}px; left: {left}px; width: {width}px; height: {height}px; --color: {color}; --zIndex: {zIndex};"
|
||||
style="top: {top}px; left: {left}px; width: {width}px; height: {height}px; --color: {color}; --zIndex: {zIndex}; --bg: {background ||
|
||||
'none'};"
|
||||
class:withText={!!text}
|
||||
class:vCompact={height < 40}
|
||||
class:hCompact={width < 40}
|
||||
class:animate
|
||||
>
|
||||
{#if text || icon}
|
||||
<div
|
||||
|
@ -84,6 +88,7 @@
|
|||
border: 2px solid var(--color);
|
||||
pointer-events: none;
|
||||
border-radius: 4px;
|
||||
background: var(--bg);
|
||||
}
|
||||
.indicator.withText {
|
||||
border-top-left-radius: 0;
|
||||
|
@ -94,6 +99,9 @@
|
|||
.indicator.line {
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
.indicator.animate {
|
||||
transition: all 130ms ease-out;
|
||||
}
|
||||
|
||||
/* Label styles */
|
||||
.label {
|
||||
|
|
|
@ -9,6 +9,10 @@
|
|||
export let zIndex = 900
|
||||
export let prefix = null
|
||||
export let allowResizeAnchors = false
|
||||
export let background = null
|
||||
export let animate = false
|
||||
export let text = null
|
||||
export let icon = null
|
||||
|
||||
// Offset = 6 (clip-root padding) - 1 (half the border thickness)
|
||||
const config = memo($$props)
|
||||
|
@ -24,8 +28,8 @@
|
|||
|
||||
// Computed state
|
||||
indicators: [],
|
||||
text: null,
|
||||
icon: null,
|
||||
text,
|
||||
icon,
|
||||
insideGrid: false,
|
||||
error: false,
|
||||
})
|
||||
|
@ -61,7 +65,6 @@
|
|||
const observeMutations = element => {
|
||||
mutationObserver.observe(element, {
|
||||
attributes: true,
|
||||
attributeFilter: ["style"],
|
||||
})
|
||||
observingMutations = true
|
||||
}
|
||||
|
@ -108,17 +111,19 @@
|
|||
}
|
||||
|
||||
// Check if we're inside a grid
|
||||
if (allowResizeAnchors) {
|
||||
nextState.insideGrid = elements[0]?.dataset.insideGrid === "true"
|
||||
}
|
||||
nextState.insideGrid = elements[0]?.dataset.insideGrid === "true"
|
||||
|
||||
// Get text to display
|
||||
nextState.text = elements[0].dataset.name
|
||||
if (nextState.prefix) {
|
||||
nextState.text = `${nextState.prefix} ${nextState.text}`
|
||||
// Get text and icon to display
|
||||
if (!text) {
|
||||
nextState.text = elements[0].dataset.name
|
||||
if (nextState.prefix) {
|
||||
nextState.text = `${nextState.prefix} ${nextState.text}`
|
||||
}
|
||||
}
|
||||
if (elements[0].dataset.icon) {
|
||||
nextState.icon = elements[0].dataset.icon
|
||||
if (!icon) {
|
||||
if (elements[0].dataset.icon) {
|
||||
nextState.icon = elements[0].dataset.icon
|
||||
}
|
||||
}
|
||||
nextState.error = elements[0].classList.contains("error")
|
||||
|
||||
|
@ -205,5 +210,7 @@
|
|||
color={state.error ? errorColor : state.color}
|
||||
componentId={state.componentId}
|
||||
zIndex={state.zIndex}
|
||||
{background}
|
||||
{animate}
|
||||
/>
|
||||
{/each}
|
||||
|
|
|
@ -21,10 +21,17 @@ const createDndStore = () => {
|
|||
}
|
||||
const store = writable(initialState)
|
||||
|
||||
const startDraggingExistingComponent = ({ id, parent, bounds, index }) => {
|
||||
const startDraggingExistingComponent = ({
|
||||
id,
|
||||
parent,
|
||||
bounds,
|
||||
index,
|
||||
name,
|
||||
icon,
|
||||
}) => {
|
||||
store.set({
|
||||
...initialState,
|
||||
source: { id, parent, bounds, index },
|
||||
source: { id, parent, bounds, index, name, icon, isNew: false },
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -62,7 +69,10 @@ const createDndStore = () => {
|
|||
parent: null,
|
||||
bounds: { height, width },
|
||||
index: null,
|
||||
newComponentType: component,
|
||||
type: component,
|
||||
isNew: true,
|
||||
name: `New ${definition.name}`,
|
||||
icon: definition.icon,
|
||||
},
|
||||
target,
|
||||
drop,
|
||||
|
@ -118,9 +128,5 @@ export const dndStore = createDndStore()
|
|||
// or components which depend on DND state unless values actually change.
|
||||
export const dndParent = derivedMemo(dndStore, x => x.drop?.parent)
|
||||
export const dndIndex = derivedMemo(dndStore, x => x.drop?.index)
|
||||
export const dndBounds = derivedMemo(dndStore, x => x.source?.bounds)
|
||||
export const dndSource = derivedMemo(dndStore, x => x.source)
|
||||
export const dndIsDragging = derivedMemo(dndStore, x => !!x.source)
|
||||
export const dndIsNewComponent = derivedMemo(
|
||||
dndStore,
|
||||
x => x.source?.newComponentType != null
|
||||
)
|
||||
|
|
|
@ -18,14 +18,7 @@ export { environmentStore } from "./environment"
|
|||
export { eventStore } from "./events"
|
||||
export { orgStore } from "./org"
|
||||
export { roleStore } from "./roles"
|
||||
export {
|
||||
dndStore,
|
||||
dndIndex,
|
||||
dndParent,
|
||||
dndBounds,
|
||||
dndIsNewComponent,
|
||||
dndIsDragging,
|
||||
} from "./dnd"
|
||||
export { dndStore, dndIndex, dndParent, dndIsDragging, dndSource } from "./dnd"
|
||||
export { sidePanelStore } from "./sidePanel"
|
||||
export { modalStore } from "./modal"
|
||||
export { hoverStore } from "./hover"
|
||||
|
|
|
@ -3,7 +3,7 @@ import { routeStore } from "./routes"
|
|||
import { builderStore } from "./builder"
|
||||
import { appStore } from "./app"
|
||||
import { orgStore } from "./org"
|
||||
import { dndIndex, dndParent, dndIsNewComponent, dndBounds } from "./dnd.js"
|
||||
import { dndIndex, dndParent, dndSource } from "./dnd.js"
|
||||
import { RoleUtils } from "@budibase/frontend-core"
|
||||
import { findComponentById, findComponentParent } from "../utils/components.js"
|
||||
import { Helpers } from "@budibase/bbui"
|
||||
|
@ -18,8 +18,7 @@ const createScreenStore = () => {
|
|||
orgStore,
|
||||
dndParent,
|
||||
dndIndex,
|
||||
dndIsNewComponent,
|
||||
dndBounds,
|
||||
dndSource,
|
||||
],
|
||||
([
|
||||
$appStore,
|
||||
|
@ -28,8 +27,7 @@ const createScreenStore = () => {
|
|||
$orgStore,
|
||||
$dndParent,
|
||||
$dndIndex,
|
||||
$dndIsNewComponent,
|
||||
$dndBounds,
|
||||
$dndSource,
|
||||
]) => {
|
||||
let activeLayout, activeScreen
|
||||
let screens
|
||||
|
@ -85,7 +83,7 @@ const createScreenStore = () => {
|
|||
|
||||
// Remove selected component from tree if we are moving an existing
|
||||
// component
|
||||
if (!$dndIsNewComponent && selectedParent) {
|
||||
if (!$dndSource.isNew && selectedParent) {
|
||||
selectedParent._children = selectedParent._children?.filter(
|
||||
x => x._id !== selectedComponentId
|
||||
)
|
||||
|
@ -97,11 +95,11 @@ const createScreenStore = () => {
|
|||
_id: DNDPlaceholderID,
|
||||
_styles: {
|
||||
normal: {
|
||||
width: `${$dndBounds?.width || 400}px`,
|
||||
height: `${$dndBounds?.height || 200}px`,
|
||||
width: `${$dndSource?.bounds?.width || 400}px`,
|
||||
height: `${$dndSource?.bounds?.height || 200}px`,
|
||||
opacity: 0,
|
||||
"--default-width": $dndBounds?.width || 400,
|
||||
"--default-height": $dndBounds?.height || 200,
|
||||
"--default-width": $dndSource?.bounds?.width || 400,
|
||||
"--default-height": $dndSource?.bounds?.height || 200,
|
||||
},
|
||||
},
|
||||
static: true,
|
||||
|
|
Loading…
Reference in New Issue