mirror of https://github.com/Budibase/budibase.git
5 changed files with 139 additions and 116 deletions
@ -1,86 +1,32 @@ |
|||
<script> |
|||
import { onMount, onDestroy } from "svelte" |
|||
import IndicatorSet from "./IndicatorSet.svelte" |
|||
import { builderStore } from "../store" |
|||
import Indicator from "./Indicator.svelte" |
|||
import { domDebounce } from "../utils/domDebounce" |
|||
|
|||
let indicators = [] |
|||
let interval |
|||
let componentId |
|||
let componentName |
|||
|
|||
const updatePosition = () => { |
|||
let newIndicators = [] |
|||
|
|||
if (componentId) { |
|||
const parents = document.getElementsByClassName(componentId) |
|||
|
|||
// Batch reads to minimize reflow |
|||
const scrollX = window.scrollX |
|||
const scrollY = window.scrollY |
|||
|
|||
for (let i = 0; i < parents.length; i++) { |
|||
const child = parents[i]?.childNodes?.[0] |
|||
if (child) { |
|||
const elBounds = child.getBoundingClientRect() |
|||
newIndicators.push({ |
|||
top: elBounds.top + scrollY - 2, |
|||
left: elBounds.left + scrollX - 2, |
|||
width: elBounds.width + 4, |
|||
height: elBounds.height + 4, |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
|
|||
indicators = newIndicators |
|||
} |
|||
const debouncedUpdate = domDebounce(updatePosition) |
|||
$: zIndex = componentId === $builderStore.selectedComponentId ? 900 : 920 |
|||
|
|||
const onMouseOver = e => { |
|||
const element = e.target.closest("[data-type='component']") |
|||
const newId = element?.dataset?.id |
|||
const newName = element?.dataset?.name |
|||
if (newId !== componentId) { |
|||
componentId = newId |
|||
componentName = newName |
|||
debouncedUpdate() |
|||
} |
|||
} |
|||
|
|||
const onMouseLeave = () => { |
|||
componentId = null |
|||
componentName = null |
|||
} |
|||
|
|||
onMount(() => { |
|||
debouncedUpdate() |
|||
interval = setInterval(debouncedUpdate, 100) |
|||
document.addEventListener("mouseover", onMouseOver) |
|||
document.addEventListener("mouseleave", onMouseLeave) |
|||
document.addEventListener("scroll", debouncedUpdate, true) |
|||
}) |
|||
|
|||
onDestroy(() => { |
|||
clearInterval(interval) |
|||
document.removeEventListener("mouseover", onMouseOver) |
|||
document.removeEventListener("mouseleave", onMouseLeave) |
|||
document.removeEventListener("scroll", debouncedUpdate, true) |
|||
}) |
|||
</script> |
|||
|
|||
{#key componentId} |
|||
{#if componentId !== $builderStore.selectedComponentId} |
|||
{#each indicators as indicator, idx} |
|||
<Indicator |
|||
top={indicator.top} |
|||
left={indicator.left} |
|||
width={indicator.width} |
|||
height={indicator.height} |
|||
text={idx === 0 ? componentName : null} |
|||
color="rgb(120, 170, 244)" |
|||
transition |
|||
/> |
|||
{/each} |
|||
{/if} |
|||
{/key} |
|||
<IndicatorSet {componentId} color="rgb(120, 170, 244)" transition {zIndex} /> |
|||
|
|||
@ -0,0 +1,110 @@ |
|||
<script> |
|||
import { onMount, onDestroy } from "svelte" |
|||
import Indicator from "./Indicator.svelte" |
|||
import { domDebounce } from "../utils/domDebounce" |
|||
|
|||
export let componentId |
|||
export let color |
|||
export let transition |
|||
export let zIndex |
|||
|
|||
let indicators = [] |
|||
let interval |
|||
let text |
|||
$: visibleIndicators = indicators.filter(x => x.visible) |
|||
|
|||
let updating = false |
|||
let observers = [] |
|||
let callbackCount = 0 |
|||
let nextIndicators = [] |
|||
|
|||
const createIntersectionCallback = idx => entries => { |
|||
if (callbackCount >= observers.length) { |
|||
return |
|||
} |
|||
nextIndicators[idx].visible = entries[0].isIntersecting |
|||
if (++callbackCount === observers.length) { |
|||
indicators = nextIndicators |
|||
updating = false |
|||
} |
|||
} |
|||
|
|||
const updatePosition = () => { |
|||
if (updating) { |
|||
return |
|||
} |
|||
|
|||
// Sanity check |
|||
if (!componentId) { |
|||
indicators = [] |
|||
return |
|||
} |
|||
|
|||
// Reset state |
|||
updating = true |
|||
callbackCount = 0 |
|||
observers.forEach(o => o.disconnect()) |
|||
observers = [] |
|||
nextIndicators = [] |
|||
|
|||
// Determine next set of indicators |
|||
const parents = document.getElementsByClassName(componentId) |
|||
if (parents.length) { |
|||
text = parents[0].dataset.name |
|||
} |
|||
|
|||
// Batch reads to minimize reflow |
|||
const scrollX = window.scrollX |
|||
const scrollY = window.scrollY |
|||
|
|||
// Extract valid children |
|||
const children = Array.from(parents) |
|||
.map(parent => parent?.childNodes?.[0]) |
|||
.filter(child => child != null) |
|||
|
|||
children.forEach((child, idx) => { |
|||
const callback = createIntersectionCallback(idx) |
|||
const threshold = children.length > 1 ? 1 : 0 |
|||
const observer = new IntersectionObserver(callback, { threshold }) |
|||
observer.observe(child) |
|||
observers.push(observer) |
|||
|
|||
const elBounds = child.getBoundingClientRect() |
|||
nextIndicators.push({ |
|||
top: elBounds.top + scrollY - 2, |
|||
left: elBounds.left + scrollX - 2, |
|||
width: elBounds.width + 4, |
|||
height: elBounds.height + 4, |
|||
visible: false, |
|||
}) |
|||
}) |
|||
} |
|||
const debouncedUpdate = domDebounce(updatePosition) |
|||
|
|||
onMount(() => { |
|||
debouncedUpdate() |
|||
interval = setInterval(debouncedUpdate, 100) |
|||
document.addEventListener("scroll", debouncedUpdate, true) |
|||
}) |
|||
|
|||
onDestroy(() => { |
|||
clearInterval(interval) |
|||
document.removeEventListener("scroll", debouncedUpdate, true) |
|||
observers.forEach(o => o.disconnect()) |
|||
}) |
|||
</script> |
|||
|
|||
{#key componentId} |
|||
{#each visibleIndicators as indicator, idx} |
|||
<Indicator |
|||
top={indicator.top} |
|||
left={indicator.left} |
|||
width={indicator.width} |
|||
height={indicator.height} |
|||
text={idx === 0 ? text : null} |
|||
{transition} |
|||
{zIndex} |
|||
{color} |
|||
/> |
|||
{/each} |
|||
{/key} |
|||
@ -1,56 +1,11 @@ |
|||
<script> |
|||
import { onMount, onDestroy } from "svelte" |
|||
import { builderStore } from "../store" |
|||
import Indicator from "./Indicator.svelte" |
|||
import { domDebounce } from "../utils/domDebounce" |
|||
|
|||
let indicators = [] |
|||
let interval |
|||
|
|||
const updatePosition = () => { |
|||
const id = $builderStore.selectedComponentId |
|||
const parents = document.getElementsByClassName(id) |
|||
|
|||
// Batch reads to minimize reflow |
|||
const scrollX = window.scrollX |
|||
const scrollY = window.scrollY |
|||
|
|||
let newIndicators = [] |
|||
for (let i = 0; i < parents.length; i++) { |
|||
const child = parents[i]?.childNodes?.[0] |
|||
if (child) { |
|||
const elBounds = child.getBoundingClientRect() |
|||
newIndicators.push({ |
|||
top: elBounds.top + scrollY - 2, |
|||
left: elBounds.left + scrollX - 2, |
|||
width: elBounds.width + 4, |
|||
height: elBounds.height + 4, |
|||
}) |
|||
} |
|||
} |
|||
indicators = newIndicators |
|||
} |
|||
const debouncedUpdate = domDebounce(updatePosition) |
|||
|
|||
onMount(() => { |
|||
debouncedUpdate() |
|||
interval = setInterval(debouncedUpdate, 100) |
|||
document.addEventListener("scroll", debouncedUpdate, true) |
|||
}) |
|||
|
|||
onDestroy(() => { |
|||
clearInterval(interval) |
|||
document.removeEventListener("scroll", debouncedUpdate, true) |
|||
}) |
|||
import IndicatorSet from "./IndicatorSet.svelte" |
|||
</script> |
|||
|
|||
{#each indicators as indicator, idx} |
|||
<Indicator |
|||
top={indicator.top} |
|||
left={indicator.left} |
|||
width={indicator.width} |
|||
height={indicator.height} |
|||
text={idx === 0 ? $builderStore.selectedComponent._instanceName : null} |
|||
color="rgb(66, 133, 244)" |
|||
/> |
|||
{/each} |
|||
<IndicatorSet |
|||
componentId={$builderStore.selectedComponentId} |
|||
color="rgb(66, 133, 244)" |
|||
zIndex="910" |
|||
transition |
|||
/> |
|||
|
|||
Loading…
Reference in new issue