mirror of https://github.com/Budibase/budibase.git
5 changed files with 139 additions and 116 deletions
@ -1,86 +1,32 @@ |
|||||
<script> |
<script> |
||||
import { onMount, onDestroy } from "svelte" |
import { onMount, onDestroy } from "svelte" |
||||
|
import IndicatorSet from "./IndicatorSet.svelte" |
||||
import { builderStore } from "../store" |
import { builderStore } from "../store" |
||||
import Indicator from "./Indicator.svelte" |
|
||||
import { domDebounce } from "../utils/domDebounce" |
|
||||
|
|
||||
let indicators = [] |
|
||||
let interval |
|
||||
let componentId |
let componentId |
||||
let componentName |
$: zIndex = componentId === $builderStore.selectedComponentId ? 900 : 920 |
||||
|
|
||||
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) |
|
||||
|
|
||||
const onMouseOver = e => { |
const onMouseOver = e => { |
||||
const element = e.target.closest("[data-type='component']") |
const element = e.target.closest("[data-type='component']") |
||||
const newId = element?.dataset?.id |
const newId = element?.dataset?.id |
||||
const newName = element?.dataset?.name |
|
||||
if (newId !== componentId) { |
if (newId !== componentId) { |
||||
componentId = newId |
componentId = newId |
||||
componentName = newName |
|
||||
debouncedUpdate() |
|
||||
} |
} |
||||
} |
} |
||||
|
|
||||
const onMouseLeave = () => { |
const onMouseLeave = () => { |
||||
componentId = null |
componentId = null |
||||
componentName = null |
|
||||
} |
} |
||||
|
|
||||
onMount(() => { |
onMount(() => { |
||||
debouncedUpdate() |
|
||||
interval = setInterval(debouncedUpdate, 100) |
|
||||
document.addEventListener("mouseover", onMouseOver) |
document.addEventListener("mouseover", onMouseOver) |
||||
document.addEventListener("mouseleave", onMouseLeave) |
document.addEventListener("mouseleave", onMouseLeave) |
||||
document.addEventListener("scroll", debouncedUpdate, true) |
|
||||
}) |
}) |
||||
|
|
||||
onDestroy(() => { |
onDestroy(() => { |
||||
clearInterval(interval) |
|
||||
document.removeEventListener("mouseover", onMouseOver) |
document.removeEventListener("mouseover", onMouseOver) |
||||
document.removeEventListener("mouseleave", onMouseLeave) |
document.removeEventListener("mouseleave", onMouseLeave) |
||||
document.removeEventListener("scroll", debouncedUpdate, true) |
|
||||
}) |
}) |
||||
</script> |
</script> |
||||
|
|
||||
{#key componentId} |
<IndicatorSet {componentId} color="rgb(120, 170, 244)" transition {zIndex} /> |
||||
{#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} |
|
||||
|
|||||
@ -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> |
<script> |
||||
import { onMount, onDestroy } from "svelte" |
|
||||
import { builderStore } from "../store" |
import { builderStore } from "../store" |
||||
import Indicator from "./Indicator.svelte" |
import IndicatorSet from "./IndicatorSet.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) |
|
||||
}) |
|
||||
</script> |
</script> |
||||
|
|
||||
{#each indicators as indicator, idx} |
<IndicatorSet |
||||
<Indicator |
componentId={$builderStore.selectedComponentId} |
||||
top={indicator.top} |
color="rgb(66, 133, 244)" |
||||
left={indicator.left} |
zIndex="910" |
||||
width={indicator.width} |
transition |
||||
height={indicator.height} |
/> |
||||
text={idx === 0 ? $builderStore.selectedComponent._instanceName : null} |
|
||||
color="rgb(66, 133, 244)" |
|
||||
/> |
|
||||
{/each} |
|
||||
|
|||||
Loading…
Reference in new issue