Improve reactivity, simplify logic and fix EventHandler type

This commit is contained in:
Andrew Kingston 2025-02-03 10:51:29 +00:00
parent 4803bbd2a4
commit d4ffd0509e
No known key found for this signature in database
2 changed files with 118 additions and 165 deletions

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { onMount } from "svelte" import { onMount } from "svelte"
import { Select } from "@budibase/bbui" import { Select } from "@budibase/bbui"
import type { Component } from "@budibase/types" import type { Component, EventHandler } from "@budibase/types"
import { getAllStateVariables, getBindableProperties } from "@/dataBinding" import { getAllStateVariables, getBindableProperties } from "@/dataBinding"
import { import {
componentStore, componentStore,
@ -23,113 +23,157 @@
settings: string[] settings: string[]
} }
let selectedKey: string | undefined = undefined
let componentsUsingState: ComponentUsingState[] = []
let componentsUpdatingState: ComponentUsingState[] = []
let editorValue: string = ""
$: selectStateKey(selectedKey)
$: keyOptions = getAllStateVariables($selectedScreen) $: keyOptions = getAllStateVariables($selectedScreen)
$: bindings = getBindableProperties( $: bindings = getBindableProperties(
$selectedScreen, $selectedScreen,
$componentStore.selectedComponentId $componentStore.selectedComponentId
) )
let selectedKey: string | undefined = undefined // Auto-select first valid state key
let componentsUsingState: ComponentUsingState[] = []
let componentsUpdatingState: ComponentUsingState[] = []
let editorValue: string = ""
let previousScreenId: string | undefined = undefined
$: { $: {
const hasScreenChanged = if (keyOptions.length && !keyOptions.includes(selectedKey)) {
$selectedScreen && $selectedScreen._id !== previousScreenId
const previewContext = $previewStore.selectedComponentContext || {}
if (hasScreenChanged) {
selectedKey = keyOptions[0] selectedKey = keyOptions[0]
} else if (!keyOptions.length) {
selectedKey = undefined
}
}
const selectStateKey = (key: string | undefined) => {
if (key) {
searchComponents(key)
editorValue = $previewStore.selectedComponentContext?.state?.[key] ?? ""
} else {
editorValue = ""
componentsUsingState = [] componentsUsingState = []
componentsUpdatingState = [] componentsUpdatingState = []
editorValue = ""
previousScreenId = $selectedScreen._id
} }
}
if (keyOptions.length > 0 && !keyOptions.includes(selectedKey)) { const searchComponents = (stateKey: string) => {
selectedKey = keyOptions[0] if (!$selectedScreen?.props) {
return
} }
componentsUsingState = findComponentsUsingState(
$selectedScreen.props,
stateKey
)
componentsUpdatingState = findComponentsUpdatingState(
$selectedScreen.props,
stateKey
)
if (selectedKey) { // Check screen load actions which are outside the component hierarchy
searchComponents(selectedKey) if (eventUpdatesState($selectedScreen.onLoad, stateKey)) {
editorValue = previewContext.state?.[selectedKey] ?? "" componentsUpdatingState.push({
id: $selectedScreen._id!,
name: "Screen - On load",
settings: ["onLoad"],
})
} }
} }
const eventUpdatesState = (
handlers: EventHandler[] | undefined,
stateKey: string
) => {
return handlers?.some(handler => {
return (
handler["##eventHandlerType"] === "Update State" &&
handler.parameters?.key === stateKey
)
})
}
const findComponentsUpdatingState = ( const findComponentsUpdatingState = (
component: Component, component: Component,
stateKey: string stateKey: string,
foundComponents: ComponentUsingState[] = []
): ComponentUsingState[] => { ): ComponentUsingState[] => {
let foundComponents: ComponentUsingState[] = [] // Check all settings of this component
const definition = componentStore.getDefinition(component._component)
const checkStateUpdateHandlers = (
handlers: any[],
componentId: string,
instanceName: string,
settingKey: string
) => {
if (!Array.isArray(handlers)) return
handlers.forEach(handler => {
if (
handler["##eventHandlerType"] === "Update State" &&
handler.parameters?.key === stateKey
) {
let label =
definition?.settings?.find(t => t.key === settingKey)?.label ||
settingKey
foundComponents.push({
id: componentId,
name: `${instanceName} - ${label}`,
settings: [settingKey],
})
}
})
}
componentStore componentStore
.getComponentSettings(component._component) .getComponentSettings(component._component)
.filter( .filter(s => s.type === "event" || s.type === "buttonConfiguration")
setting =>
setting.type === "event" || setting.type === "buttonConfiguration"
)
.forEach(setting => { .forEach(setting => {
if (setting.type === "event") { if (setting.type === "event") {
checkStateUpdateHandlers( if (eventUpdatesState(component[setting.key], stateKey)) {
component[setting.key], let label = setting.label || setting.key
component._id!, foundComponents.push({
component._instanceName, id: component._id!,
setting.key name: `${component._instanceName} - ${label}`,
) settings: [setting.key],
})
}
} else if (setting.type === "buttonConfiguration") { } else if (setting.type === "buttonConfiguration") {
const buttons = component[setting.key] const buttons = component[setting.key]
if (Array.isArray(buttons)) { if (Array.isArray(buttons)) {
buttons.forEach(button => { buttons.forEach(button => {
checkStateUpdateHandlers( if (eventUpdatesState(button.onClick, stateKey)) {
button.onClick, let label = setting.label || setting.key
component._id!, foundComponents.push({
component._instanceName, id: component._id!,
setting.key name: `${component._instanceName} - ${label}`,
) settings: [setting.key],
})
}
}) })
} }
} }
}) })
if (component._children) { // Check children
foundComponents.push( component._children?.forEach(child => {
...component._children.flatMap(child => findComponentsUpdatingState(child, stateKey, foundComponents)
findComponentsUpdatingState(child, stateKey) })
)
)
}
return foundComponents return foundComponents
} }
const findComponentsUsingState = (
component: Component,
stateKey: string,
componentsUsingState: ComponentUsingState[] = []
): ComponentUsingState[] => {
const settings = componentStore.getComponentSettings(component._component)
// Check all settings of this component
const settingsWithState = getSettingsUsingState(component, stateKey)
settingsWithState.forEach(setting => {
// Get readable label for this setting
let label = settings.find(s => s.key === setting)?.label || setting
if (setting === "_conditions") {
label = "Conditions"
} else if (setting === "_styles") {
label = "Styles"
}
componentsUsingState.push({
id: component._id!,
name: `${component._instanceName} - ${label}`,
settings: [setting],
})
})
// Check children
component._children?.forEach(child => {
findComponentsUsingState(child, stateKey, componentsUsingState)
})
return componentsUsingState
}
const getSettingsUsingState = (
component: Component,
stateKey: string
): string[] => {
return Object.entries(component)
.filter(([key]) => key !== "_children")
.filter(([_, value]) => hasStateBinding(JSON.stringify(value), stateKey))
.map(([key]) => key)
}
const hasStateBinding = (value: string, stateKey: string): boolean => { const hasStateBinding = (value: string, stateKey: string): boolean => {
const bindings = findHBSBlocks(value).map(binding => { const bindings = findHBSBlocks(value).map(binding => {
const sanitizedBinding = binding.replace(/\\"/g, '"') const sanitizedBinding = binding.replace(/\\"/g, '"')
@ -140,105 +184,15 @@
return bindings.join(" ").includes(stateKey) return bindings.join(" ").includes(stateKey)
} }
const getSettingsWithState = (
component: Component,
stateKey: string
): string[] => {
return Object.entries(component)
.filter(([key]) => key !== "_children")
.filter(([_, value]) => hasStateBinding(JSON.stringify(value), stateKey))
.map(([key]) => key)
}
const findComponentsUsingState = (
component: Component,
stateKey: string
): ComponentUsingState[] => {
let componentsUsingState: ComponentUsingState[] = []
let label
const { _children } = component
const definition = componentStore.getDefinition(component._component)
const settingsWithState = getSettingsWithState(component, stateKey)
settingsWithState.forEach(setting => {
label =
definition?.settings?.find(t => t.key === setting)?.label || setting
if (setting === "_conditions") {
label = "Conditions"
} else if (setting === "_styles") {
label = "Styles"
}
componentsUsingState.push({
id: component._id!,
name: `${component._instanceName} - ${label}`,
settings: [setting],
})
})
if (_children) {
for (let child of _children) {
componentsUsingState = [
...componentsUsingState,
...findComponentsUsingState(child, stateKey),
]
}
}
return componentsUsingState
}
const searchComponents = (stateKey: string | undefined) => {
if (!stateKey || !$selectedScreen?.props) {
return
}
const componentStateUpdates = findComponentsUpdatingState(
$selectedScreen.props,
stateKey
)
componentsUsingState = findComponentsUsingState(
$selectedScreen.props,
stateKey
)
const screenStateUpdates =
$selectedScreen?.onLoad
?.filter(
(handler: any) =>
handler["##eventHandlerType"] === "Update State" &&
handler.parameters?.key === stateKey
)
.map(() => ({
id: $selectedScreen._id!,
name: "Screen - On load",
settings: ["onLoad"],
})) || []
componentsUpdatingState = [...componentStateUpdates, ...screenStateUpdates]
}
const handleStateKeySelect = (key: CustomEvent) => {
if (!key.detail && keyOptions.length > 0) {
throw new Error("No state key selected")
}
searchComponents(key.detail)
}
const onClickComponentLink = (component: ComponentUsingState) => { const onClickComponentLink = (component: ComponentUsingState) => {
componentStore.select(component.id) componentStore.select(component.id)
component.settings.forEach(setting => { builderStore.highlightSetting(component.settings[0])
builderStore.highlightSetting(setting)
})
} }
const handleStateInspectorChange = (e: CustomEvent) => { const handleStateInspectorChange = (e: CustomEvent) => {
if (!selectedKey || !$previewStore.selectedComponentContext) { if (!selectedKey || !$previewStore.selectedComponentContext) {
return return
} }
const stateUpdate = { const stateUpdate = {
[selectedKey]: processStringSync( [selectedKey]: processStringSync(
e.detail, e.detail,
@ -260,7 +214,6 @@
bind:value={selectedKey} bind:value={selectedKey}
placeholder={keyOptions.length > 0 ? false : "No state variables found"} placeholder={keyOptions.length > 0 ? false : "No state variables found"}
options={keyOptions} options={keyOptions}
on:change={handleStateKeySelect}
/> />
{#if selectedKey && keyOptions.length > 0} {#if selectedKey && keyOptions.length > 0}
<DrawerBindableInput <DrawerBindableInput

View File

@ -45,6 +45,6 @@ export interface EventHandler {
value: string value: string
persist: any | null persist: any | null
} }
eventHandlerType: string "##eventHandlerType": string
id: string id: string
} }