Rewrite settings bar updates to improve performance

This commit is contained in:
Andrew Kingston 2024-08-12 14:45:17 +01:00
parent 1f99ecc529
commit d423d530e4
No known key found for this signature in database
2 changed files with 110 additions and 96 deletions

View File

@ -198,7 +198,7 @@
overflow: hidden; overflow: hidden;
height: 410px; height: 410px;
} }
div.in-builder { div.in-builder :global(> *) {
pointer-events: none !important; pointer-events: none !important;
} }
</style> </style>

View File

@ -11,25 +11,24 @@
const context = getContext("context") const context = getContext("context")
const verticalOffset = 36 const verticalOffset = 36
const horizontalOffset = 2 const horizontalOffset = 2
const observer = new MutationObserver(() => debouncedUpdate())
let top = 0 let top = 0
let left = 0 let left = 0
let interval let interval
let self let self
let measured = false let measured = false
let observer let observing = false
let insideGrid = false let insideGrid = false
let gridHAlign let gridHAlign
let gridVAlign let gridVAlign
let deviceEl
// TODO: respect dependsOn keys $: id = $builderStore.selectedComponentId
$: id, reset()
$: componentId = $builderStore.selectedComponentId
$: measured, observeComputedStyles(componentId)
$: component = $componentStore.selectedComponent $: component = $componentStore.selectedComponent
$: definition = $componentStore.selectedComponentDefinition $: definition = $componentStore.selectedComponentDefinition
$: instance = componentStore.actions.getComponentInstance(componentId) $: instance = componentStore.actions.getComponentInstance(id)
$: state = $instance?.state $: state = $instance?.state
$: showBar = $: showBar =
definition?.showSettingsBar !== false && definition?.showSettingsBar !== false &&
@ -37,7 +36,7 @@
definition && definition &&
!$state?.errorState !$state?.errorState
$: settings = getBarSettings(component, definition) $: settings = getBarSettings(component, definition)
$: isRoot = componentId === $builderStore.screen?.props?._id $: isRoot = id === $builderStore.screen?.props?._id
$: showGridStyles = $: showGridStyles =
insideGrid && insideGrid &&
(definition?.grid?.hAlign !== "stretch" || (definition?.grid?.hAlign !== "stretch" ||
@ -47,6 +46,21 @@
$: gridHAlignVar = getGridVar(device, GridParams.HAlign) $: gridHAlignVar = getGridVar(device, GridParams.HAlign)
$: gridVAlignVar = getGridVar(device, GridParams.VAlign) $: gridVAlignVar = getGridVar(device, GridParams.VAlign)
const reset = () => {
observer.disconnect()
measured = false
observing = false
insideGrid = false
}
const startObserving = domBoundary => {
observer.observe(domBoundary, {
attributes: true,
attributeFilter: ["style"],
})
observing = true
}
const getBarSettings = (component, definition) => { const getBarSettings = (component, definition) => {
let allSettings = [] let allSettings = []
definition?.settings?.forEach(setting => { definition?.settings?.forEach(setting => {
@ -68,102 +82,102 @@
if (!showBar) { if (!showBar) {
return return
} }
const id = $builderStore.selectedComponentId
let element = document.getElementsByClassName(id)?.[0]
// Check if we're inside a grid // Find DOM boundary and ensure it is valid
insideGrid = element?.parentNode.classList.contains("grid") let domBoundary = document.getElementsByClassName(id)[0]
if (!domBoundary) {
return reset()
}
// If we're inside a grid, allow time for buttons to render
const nextInsideGrid = domBoundary.dataset.insideGrid === "true"
if (nextInsideGrid && !insideGrid) {
insideGrid = true
return
} else {
insideGrid = nextInsideGrid
}
// Get the correct DOM boundary depending if we're inside a grid or not
if (!insideGrid) { if (!insideGrid) {
element = element?.children?.[0] domBoundary =
domBoundary.getElementsByClassName(`${id}-dom`)[0] ||
domBoundary.children?.[0]
}
if (!domBoundary || !self) {
return reset()
} }
// The settings bar is higher in the dom tree than the selection indicators // Start observing if required
// as we want to be able to render the settings bar wider than the screen, if (!observing) {
// or outside the screen. startObserving(domBoundary)
// Therefore we use the clip root rather than the app root to determine }
// its position.
const device = document.getElementById("clip-root")
if (element && self) {
// Batch reads to minimize reflow
const deviceBounds = device.getBoundingClientRect()
const elBounds = element.getBoundingClientRect()
const width = self.offsetWidth
const height = self.offsetHeight
const { scrollX, scrollY, innerWidth } = window
// Read grid metadata from data attributes // Batch reads to minimize reflow
if (insideGrid) { const deviceBounds = deviceEl.getBoundingClientRect()
if (mobile) { const elBounds = domBoundary.getBoundingClientRect()
gridHAlign = element.dataset.gridMobileHAlign const width = self.offsetWidth
gridVAlign = element.dataset.gridMobileVAlign const height = self.offsetHeight
} else { const { scrollX, scrollY, innerWidth } = window
gridHAlign = element.dataset.gridDesktopHAlign
gridVAlign = element.dataset.gridDesktopVAlign
}
}
// Vertically, always render above unless no room, then render inside // Read grid metadata from data attributes
let newTop = elBounds.top + scrollY - verticalOffset - height if (insideGrid) {
if (newTop < deviceBounds.top - 50) { if (mobile) {
newTop = deviceBounds.top - 50 gridHAlign = domBoundary.dataset.gridMobileHAlign
} gridVAlign = domBoundary.dataset.gridMobileVAlign
if (newTop < 0) { } else {
newTop = 0 gridHAlign = domBoundary.dataset.gridDesktopHAlign
} gridVAlign = domBoundary.dataset.gridDesktopVAlign
const deviceBottom = deviceBounds.top + deviceBounds.height
if (newTop > deviceBottom - 44) {
newTop = deviceBottom - 44
} }
}
//If element is at the very top of the screen, put the bar below the element // Vertically, always render above unless no room, then render inside
if (elBounds.top < elBounds.height && elBounds.height < 80) { let newTop = elBounds.top + scrollY - verticalOffset - height
newTop = elBounds.bottom + verticalOffset if (newTop < deviceBounds.top - 50) {
} newTop = deviceBounds.top - 50
}
if (newTop < 0) {
newTop = 0
}
const deviceBottom = deviceBounds.top + deviceBounds.height
if (newTop > deviceBottom - 44) {
newTop = deviceBottom - 44
}
// Horizontally, try to center first. //If element is at the very top of the screen, put the bar below the element
// Failing that, render to left edge of component. if (elBounds.top < elBounds.height && elBounds.height < 80) {
// Failing that, render to right edge of component, newTop = elBounds.bottom + verticalOffset
// Failing that, render to window left edge and accept defeat. }
let elCenter = elBounds.left + scrollX + elBounds.width / 2
let newLeft = elCenter - width / 2 // Horizontally, try to center first.
// Failing that, render to left edge of component.
// Failing that, render to right edge of component,
// Failing that, render to window left edge and accept defeat.
let elCenter = elBounds.left + scrollX + elBounds.width / 2
let newLeft = elCenter - width / 2
if (newLeft < 0 || newLeft + width > innerWidth) {
newLeft = elBounds.left + scrollX - horizontalOffset
if (newLeft < 0 || newLeft + width > innerWidth) { if (newLeft < 0 || newLeft + width > innerWidth) {
newLeft = elBounds.left + scrollX - horizontalOffset newLeft = elBounds.right + scrollX - width + horizontalOffset
if (newLeft < 0 || newLeft + width > innerWidth) { if (newLeft < 0 || newLeft + width > innerWidth) {
newLeft = elBounds.right + scrollX - width + horizontalOffset newLeft = horizontalOffset
if (newLeft < 0 || newLeft + width > innerWidth) {
newLeft = horizontalOffset
}
} }
} }
// Only update state when things changes to minimize renders
if (Math.round(newTop) !== Math.round(top)) {
top = newTop
}
if (Math.round(newLeft) !== Math.round(left)) {
left = newLeft
}
measured = true
} }
// Only update state when things changes to minimize renders
if (Math.round(newTop) !== Math.round(top)) {
top = newTop
}
if (Math.round(newLeft) !== Math.round(left)) {
left = newLeft
}
measured = true
} }
const debouncedUpdate = Utils.domDebounce(updatePosition) const debouncedUpdate = Utils.domDebounce(updatePosition)
const observeComputedStyles = id => {
observer?.disconnect()
const node = document.getElementsByClassName(`${id}-dom`)[0]?.parentNode
if (node) {
observer = new MutationObserver(updatePosition)
observer.observe(node, {
attributes: true,
attributeFilter: ["style"],
childList: false,
subtree: false,
})
}
}
onMount(() => { onMount(() => {
deviceEl = document.getElementById("clip-root")
debouncedUpdate() debouncedUpdate()
interval = setInterval(debouncedUpdate, 100) interval = setInterval(debouncedUpdate, 100)
document.addEventListener("scroll", debouncedUpdate, true) document.addEventListener("scroll", debouncedUpdate, true)
@ -172,7 +186,7 @@
onDestroy(() => { onDestroy(() => {
clearInterval(interval) clearInterval(interval)
document.removeEventListener("scroll", debouncedUpdate, true) document.removeEventListener("scroll", debouncedUpdate, true)
observer?.disconnect() reset()
}) })
</script> </script>
@ -190,7 +204,7 @@
icon="AlignLeft" icon="AlignLeft"
title="Align left" title="Align left"
active={gridHAlign === "start"} active={gridHAlign === "start"}
{componentId} componentId={id}
/> />
<GridStylesButton <GridStylesButton
style={gridHAlignVar} style={gridHAlignVar}
@ -198,7 +212,7 @@
icon="AlignCenter" icon="AlignCenter"
title="Align center" title="Align center"
active={gridHAlign === "center"} active={gridHAlign === "center"}
{componentId} componentId={id}
/> />
<GridStylesButton <GridStylesButton
style={gridHAlignVar} style={gridHAlignVar}
@ -206,7 +220,7 @@
icon="AlignRight" icon="AlignRight"
title="Align right" title="Align right"
active={gridHAlign === "end"} active={gridHAlign === "end"}
{componentId} componentId={id}
/> />
<GridStylesButton <GridStylesButton
style={gridHAlignVar} style={gridHAlignVar}
@ -214,7 +228,7 @@
icon="MoveLeftRight" icon="MoveLeftRight"
title="Stretch horizontally" title="Stretch horizontally"
active={gridHAlign === "stretch"} active={gridHAlign === "stretch"}
{componentId} componentId={id}
/> />
<div class="divider" /> <div class="divider" />
<GridStylesButton <GridStylesButton
@ -223,7 +237,7 @@
icon="AlignTop" icon="AlignTop"
title="Align top" title="Align top"
active={gridVAlign === "start"} active={gridVAlign === "start"}
{componentId} componentId={id}
/> />
<GridStylesButton <GridStylesButton
style={gridVAlignVar} style={gridVAlignVar}
@ -231,7 +245,7 @@
icon="AlignMiddle" icon="AlignMiddle"
title="Align middle" title="Align middle"
active={gridVAlign === "center"} active={gridVAlign === "center"}
{componentId} componentId={id}
/> />
<GridStylesButton <GridStylesButton
style={gridVAlignVar} style={gridVAlignVar}
@ -239,7 +253,7 @@
icon="AlignBottom" icon="AlignBottom"
title="Align bottom" title="Align bottom"
active={gridVAlign === "end"} active={gridVAlign === "end"}
{componentId} componentId={id}
/> />
<GridStylesButton <GridStylesButton
style={gridVAlignVar} style={gridVAlignVar}
@ -247,7 +261,7 @@
icon="MoveUpDown" icon="MoveUpDown"
title="Stretch vertically" title="Stretch vertically"
active={gridVAlign === "stretch"} active={gridVAlign === "stretch"}
{componentId} componentId={id}
/> />
<div class="divider" /> <div class="divider" />
{/if} {/if}