Improve reactivity, simplify logic and fix EventHandler type
This commit is contained in:
parent
4803bbd2a4
commit
d4ffd0509e
|
@ -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
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check screen load actions which are outside the component hierarchy
|
||||||
|
if (eventUpdatesState($selectedScreen.onLoad, stateKey)) {
|
||||||
|
componentsUpdatingState.push({
|
||||||
|
id: $selectedScreen._id!,
|
||||||
|
name: "Screen - On load",
|
||||||
|
settings: ["onLoad"],
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedKey) {
|
const eventUpdatesState = (
|
||||||
searchComponents(selectedKey)
|
handlers: EventHandler[] | undefined,
|
||||||
editorValue = previewContext.state?.[selectedKey] ?? ""
|
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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue