Merge pull request #12584 from Budibase/fix-hover-component-spam

Massively improve performance of hovering components
This commit is contained in:
deanhannigan 2024-01-10 12:30:59 +00:00 committed by GitHub
commit a59c7c2c33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 71 additions and 74 deletions

View File

@ -85,7 +85,6 @@ const INITIAL_FRONTEND_STATE = {
selectedScreenId: null, selectedScreenId: null,
selectedComponentId: null, selectedComponentId: null,
selectedLayoutId: null, selectedLayoutId: null,
hoverComponentId: null,
// Client state // Client state
selectedComponentInstance: null, selectedComponentInstance: null,
@ -93,6 +92,9 @@ const INITIAL_FRONTEND_STATE = {
// Onboarding // Onboarding
onboarding: false, onboarding: false,
tourNodes: null, tourNodes: null,
// UI state
hoveredComponentId: null,
} }
export const getFrontendStore = () => { export const getFrontendStore = () => {
@ -1413,6 +1415,18 @@ export const getFrontendStore = () => {
return state return state
}) })
}, },
hover: (componentId, notifyClient = true) => {
if (componentId === get(store).hoveredComponentId) {
return
}
store.update(state => {
state.hoveredComponentId = componentId
return state
})
if (notifyClient) {
store.actions.preview.sendEvent("hover-component", componentId)
}
},
}, },
links: { links: {
save: async (url, title) => { save: async (url, title) => {

View File

@ -36,14 +36,12 @@
// Determine selected component ID // Determine selected component ID
$: selectedComponentId = $store.selectedComponentId $: selectedComponentId = $store.selectedComponentId
$: hoverComponentId = $store.hoverComponentId
$: previewData = { $: previewData = {
appId: $store.appId, appId: $store.appId,
layout, layout,
screen, screen,
selectedComponentId, selectedComponentId,
hoverComponentId,
theme: $store.theme, theme: $store.theme,
customTheme: $store.customTheme, customTheme: $store.customTheme,
previewDevice: $store.previewDevice, previewDevice: $store.previewDevice,
@ -119,8 +117,8 @@
error = event.error || "An unknown error occurred" error = event.error || "An unknown error occurred"
} else if (type === "select-component" && data.id) { } else if (type === "select-component" && data.id) {
$store.selectedComponentId = data.id $store.selectedComponentId = data.id
} else if (type === "hover-component" && data.id) { } else if (type === "hover-component") {
$store.hoverComponentId = data.id store.actions.components.hover(data.id, false)
} else if (type === "update-prop") { } else if (type === "update-prop") {
await store.actions.components.updateSetting(data.prop, data.value) await store.actions.components.updateSetting(data.prop, data.value)
} else if (type === "update-styles") { } else if (type === "update-styles") {

View File

@ -90,16 +90,7 @@
return findComponentPath($selectedComponent, component._id)?.length > 0 return findComponentPath($selectedComponent, component._id)?.length > 0
} }
const handleMouseover = componentId => { const hover = store.actions.components.hover
if ($store.hoverComponentId !== componentId) {
$store.hoverComponentId = componentId
}
}
const handleMouseout = componentId => {
if ($store.hoverComponentId === componentId) {
$store.hoverComponentId = null
}
}
</script> </script>
<ul> <ul>
@ -120,9 +111,9 @@
on:dragover={dragover(component, index)} on:dragover={dragover(component, index)}
on:iconClick={() => toggleNodeOpen(component._id)} on:iconClick={() => toggleNodeOpen(component._id)}
on:drop={onDrop} on:drop={onDrop}
hovering={$store.hoverComponentId === component._id} hovering={$store.hoveredComponentId === component._id}
on:mouseenter={() => handleMouseover(component._id)} on:mouseenter={() => hover(component._id)}
on:mouseleave={() => handleMouseout(component._id)} on:mouseleave={() => hover(null)}
text={getComponentText(component)} text={getComponentText(component)}
icon={getComponentIcon(component)} icon={getComponentIcon(component)}
iconTooltip={getComponentName(component)} iconTooltip={getComponentName(component)}

View File

@ -12,6 +12,9 @@
let scrolling = false let scrolling = false
$: screenComponentId = `${$store.selectedScreenId}-screen`
$: navComponentId = `${$store.selectedScreenId}-navigation`
const toNewComponentRoute = () => { const toNewComponentRoute = () => {
if ($isActive(`./:componentId/new`)) { if ($isActive(`./:componentId/new`)) {
$goto(`./:componentId`) $goto(`./:componentId`)
@ -33,16 +36,7 @@
scrolling = e.target.scrollTop !== 0 scrolling = e.target.scrollTop !== 0
} }
const handleMouseover = componentId => { const hover = store.actions.components.hover
if ($store.hoverComponentId !== componentId) {
$store.hoverComponentId = componentId
}
}
const handleMouseout = componentId => {
if ($store.hoverComponentId === componentId) {
$store.hoverComponentId = null
}
}
</script> </script>
<div class="components"> <div class="components">
@ -65,46 +59,31 @@
scrollable scrollable
icon="WebPage" icon="WebPage"
on:drop={onDrop} on:drop={onDrop}
on:click={() => { on:click={() => ($store.selectedComponentId = screenComponentId)}
$store.selectedComponentId = `${$store.selectedScreenId}-screen` hovering={$store.hoveredComponentId === screenComponentId}
}} on:mouseenter={() => hover(screenComponentId)}
hovering={$store.hoverComponentId === on:mouseleave={() => hover(null)}
`${$store.selectedScreenId}-screen`} id="component-screen"
on:mouseenter={() => selectedBy={$userSelectedResourceMap[screenComponentId]}
handleMouseover(`${$store.selectedScreenId}-screen`)}
on:mouseleave={() =>
handleMouseout(`${$store.selectedScreenId}-screen`)}
id={`component-screen`}
selectedBy={$userSelectedResourceMap[
`${$store.selectedScreenId}-screen`
]}
> >
<ScreenslotDropdownMenu component={$selectedScreen?.props} /> <ScreenslotDropdownMenu component={$selectedScreen?.props} />
</NavItem> </NavItem>
<NavItem <NavItem
text="Navigation" text="Navigation"
indentLevel={0} indentLevel={0}
selected={$store.selectedComponentId === selected={$store.selectedComponentId === navComponentId}
`${$store.selectedScreenId}-navigation`}
opened opened
scrollable scrollable
icon={$selectedScreen.showNavigation icon={$selectedScreen.showNavigation
? "Visibility" ? "Visibility"
: "VisibilityOff"} : "VisibilityOff"}
on:drop={onDrop} on:drop={onDrop}
on:click={() => { on:click={() => ($store.selectedComponentId = navComponentId)}
$store.selectedComponentId = `${$store.selectedScreenId}-navigation` hovering={$store.hoveredComponentId === navComponentId}
}} on:mouseenter={() => hover(navComponentId)}
hovering={$store.hoverComponentId === on:mouseleave={() => hover(null)}
`${$store.selectedScreenId}-navigation`} id="component-nav"
on:mouseenter={() => selectedBy={$userSelectedResourceMap[navComponentId]}
handleMouseover(`${$store.selectedScreenId}-navigation`)}
on:mouseleave={() =>
handleMouseout(`${$store.selectedScreenId}-navigation`)}
id={`component-nav`}
selectedBy={$userSelectedResourceMap[
`${$store.selectedScreenId}-navigation`
]}
/> />
<ComponentTree <ComponentTree
level={0} level={0}

View File

@ -1,9 +1,9 @@
<script> <script>
import { onMount, onDestroy } from "svelte" import { onMount, onDestroy } from "svelte"
import IndicatorSet from "./IndicatorSet.svelte" import IndicatorSet from "./IndicatorSet.svelte"
import { builderStore, dndIsDragging } from "stores" import { builderStore, dndIsDragging, hoverStore } from "stores"
$: componentId = $builderStore.hoverComponentId $: componentId = $hoverStore.hoveredComponentId
$: zIndex = componentId === $builderStore.selectedComponentId ? 900 : 920 $: zIndex = componentId === $builderStore.selectedComponentId ? 900 : 920
const onMouseOver = e => { const onMouseOver = e => {
@ -23,12 +23,12 @@
} }
if (newId !== componentId) { if (newId !== componentId) {
builderStore.actions.hoverComponent(newId) hoverStore.actions.hoverComponent(newId)
} }
} }
const onMouseLeave = () => { const onMouseLeave = () => {
builderStore.actions.hoverComponent(null) hoverStore.actions.hoverComponent(null)
} }
onMount(() => { onMount(() => {

View File

@ -8,6 +8,7 @@ import {
environmentStore, environmentStore,
dndStore, dndStore,
eventStore, eventStore,
hoverStore,
} from "./stores" } from "./stores"
import loadSpectrumIcons from "@budibase/bbui/spectrum-icons-rollup.js" import loadSpectrumIcons from "@budibase/bbui/spectrum-icons-rollup.js"
import { get } from "svelte/store" import { get } from "svelte/store"
@ -33,7 +34,6 @@ const loadBudibase = async () => {
layout: window["##BUDIBASE_PREVIEW_LAYOUT##"], layout: window["##BUDIBASE_PREVIEW_LAYOUT##"],
screen: window["##BUDIBASE_PREVIEW_SCREEN##"], screen: window["##BUDIBASE_PREVIEW_SCREEN##"],
selectedComponentId: window["##BUDIBASE_SELECTED_COMPONENT_ID##"], selectedComponentId: window["##BUDIBASE_SELECTED_COMPONENT_ID##"],
hoverComponentId: window["##BUDIBASE_HOVER_COMPONENT_ID##"],
previewId: window["##BUDIBASE_PREVIEW_ID##"], previewId: window["##BUDIBASE_PREVIEW_ID##"],
theme: window["##BUDIBASE_PREVIEW_THEME##"], theme: window["##BUDIBASE_PREVIEW_THEME##"],
customTheme: window["##BUDIBASE_PREVIEW_CUSTOM_THEME##"], customTheme: window["##BUDIBASE_PREVIEW_CUSTOM_THEME##"],
@ -84,6 +84,8 @@ const loadBudibase = async () => {
} else { } else {
dndStore.actions.reset() dndStore.actions.reset()
} }
} else if (type === "hover-component") {
hoverStore.actions.hoverComponent(data)
} else if (type === "builder-meta") { } else if (type === "builder-meta") {
builderStore.actions.setMetadata(data) builderStore.actions.setMetadata(data)
} }

View File

@ -8,7 +8,6 @@ const createBuilderStore = () => {
inBuilder: false, inBuilder: false,
screen: null, screen: null,
selectedComponentId: null, selectedComponentId: null,
hoverComponentId: null,
editMode: false, editMode: false,
previewId: null, previewId: null,
theme: null, theme: null,
@ -25,16 +24,6 @@ const createBuilderStore = () => {
} }
const store = writable(initialState) const store = writable(initialState)
const actions = { const actions = {
hoverComponent: id => {
if (id === get(store).hoverComponentId) {
return
}
store.update(state => ({
...state,
hoverComponentId: id,
}))
eventStore.actions.dispatchEvent("hover-component", { id })
},
selectComponent: id => { selectComponent: id => {
if (id === get(store).selectedComponentId) { if (id === get(store).selectedComponentId) {
return return

View File

@ -0,0 +1,25 @@
import { get, writable } from "svelte/store"
import { eventStore } from "./events.js"
const createHoverStore = () => {
const store = writable({
hoveredComponentId: null,
})
const hoverComponent = id => {
if (id === get(store).hoveredComponentId) {
return
}
store.set({ hoveredComponentId: id })
eventStore.actions.dispatchEvent("hover-component", { id })
}
return {
...store,
actions: {
hoverComponent,
},
}
}
export const hoverStore = createHoverStore()

View File

@ -27,6 +27,7 @@ export {
dndIsDragging, dndIsDragging,
} from "./dnd" } from "./dnd"
export { sidePanelStore } from "./sidePanel" export { sidePanelStore } from "./sidePanel"
export { hoverStore } from "./hover"
// Context stores are layered and duplicated, so it is not a singleton // Context stores are layered and duplicated, so it is not a singleton
export { createContextStore } from "./context" export { createContextStore } from "./context"

View File

@ -63,7 +63,6 @@
// Extract data from message // Extract data from message
const { const {
selectedComponentId, selectedComponentId,
hoverComponentId,
layout, layout,
screen, screen,
appId, appId,
@ -82,7 +81,6 @@
window["##BUDIBASE_PREVIEW_LAYOUT##"] = layout window["##BUDIBASE_PREVIEW_LAYOUT##"] = layout
window["##BUDIBASE_PREVIEW_SCREEN##"] = screen window["##BUDIBASE_PREVIEW_SCREEN##"] = screen
window["##BUDIBASE_SELECTED_COMPONENT_ID##"] = selectedComponentId window["##BUDIBASE_SELECTED_COMPONENT_ID##"] = selectedComponentId
window["##BUDIBASE_HOVER_COMPONENT_ID##"] = hoverComponentId
window["##BUDIBASE_PREVIEW_ID##"] = Math.random() window["##BUDIBASE_PREVIEW_ID##"] = Math.random()
window["##BUDIBASE_PREVIEW_THEME##"] = theme window["##BUDIBASE_PREVIEW_THEME##"] = theme
window["##BUDIBASE_PREVIEW_CUSTOM_THEME##"] = customTheme window["##BUDIBASE_PREVIEW_CUSTOM_THEME##"] = customTheme