Cache the determination of client component settings and simplify initialisationlogic
This commit is contained in:
parent
d59f735d98
commit
7c514df39d
|
@ -1,10 +1,15 @@
|
||||||
<script context="module">
|
<script context="module">
|
||||||
|
// Cache the definition of settings for each component type
|
||||||
let SettingsDefinitionCache = {}
|
let SettingsDefinitionCache = {}
|
||||||
|
|
||||||
|
// Cache the settings of each component ID.
|
||||||
|
// This speeds up remounting as well as repeaters.
|
||||||
|
let InstanceSettingsCache = {}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { getContext, setContext } from "svelte"
|
import { getContext, setContext } from "svelte"
|
||||||
import { writable } from "svelte/store"
|
import { writable, get } from "svelte/store"
|
||||||
import * as AppComponents from "components/app"
|
import * as AppComponents from "components/app"
|
||||||
import Router from "./Router.svelte"
|
import Router from "./Router.svelte"
|
||||||
import { enrichProps, propsAreSame } from "utils/componentProps"
|
import { enrichProps, propsAreSame } from "utils/componentProps"
|
||||||
|
@ -37,8 +42,6 @@
|
||||||
let staticSettings
|
let staticSettings
|
||||||
let nestedSettings
|
let nestedSettings
|
||||||
|
|
||||||
// The context keys that
|
|
||||||
|
|
||||||
// The enriched component settings
|
// The enriched component settings
|
||||||
let enrichedSettings
|
let enrichedSettings
|
||||||
|
|
||||||
|
@ -70,10 +73,12 @@
|
||||||
const componentStore = writable({})
|
const componentStore = writable({})
|
||||||
setContext("component", componentStore)
|
setContext("component", componentStore)
|
||||||
|
|
||||||
|
//
|
||||||
|
let constructor
|
||||||
|
let definition
|
||||||
|
$: initialise(instance)
|
||||||
|
|
||||||
// Extract component instance info
|
// Extract component instance info
|
||||||
$: constructor = getComponentConstructor(instance._component)
|
|
||||||
$: definition = getComponentDefinition(instance._component)
|
|
||||||
$: settingsDefinition = getSettingsDefinition(definition)
|
|
||||||
$: children = instance._children || []
|
$: children = instance._children || []
|
||||||
$: id = instance._id
|
$: id = instance._id
|
||||||
$: name = instance._instanceName
|
$: name = instance._instanceName
|
||||||
|
@ -107,13 +112,6 @@
|
||||||
$: empty = interactive && !children.length && hasChildren
|
$: empty = interactive && !children.length && hasChildren
|
||||||
$: emptyState = empty && showEmptyState
|
$: emptyState = empty && showEmptyState
|
||||||
|
|
||||||
// Raw settings are all settings excluding internal props and children
|
|
||||||
$: rawSettings = getRawSettings(instance)
|
|
||||||
$: instanceKey = Helpers.hashString(JSON.stringify(rawSettings))
|
|
||||||
|
|
||||||
// Parse and split component settings into categories
|
|
||||||
$: updateSettings(rawSettings, instanceKey, settingsDefinition)
|
|
||||||
|
|
||||||
// Enrich component settings
|
// Enrich component settings
|
||||||
$: enrichComponentSettings(dynamicSettings, $context)
|
$: enrichComponentSettings(dynamicSettings, $context)
|
||||||
|
|
||||||
|
@ -147,15 +145,48 @@
|
||||||
editing,
|
editing,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Extracts all settings from the component instance
|
const initialise = instance => {
|
||||||
const getRawSettings = instance => {
|
if (instance == null) {
|
||||||
let validSettings = {}
|
return
|
||||||
Object.entries(instance)
|
}
|
||||||
.filter(([name]) => name === "_conditions" || !name.startsWith("_"))
|
|
||||||
.forEach(([key, value]) => {
|
// Ensure we're processing a new instance
|
||||||
validSettings[key] = value
|
const instanceKey = Helpers.hashString(JSON.stringify(instance))
|
||||||
})
|
if (instanceKey === lastInstanceKey) {
|
||||||
return validSettings
|
return
|
||||||
|
} else {
|
||||||
|
lastInstanceKey = instanceKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pull definition and constructor
|
||||||
|
constructor = getComponentConstructor(instance._component)
|
||||||
|
definition = getComponentDefinition(instance._component)
|
||||||
|
if (!definition) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the settings definition for this component, and cache it
|
||||||
|
let settingsDefinition
|
||||||
|
if (SettingsDefinitionCache[definition.name]) {
|
||||||
|
settingsDefinition = SettingsDefinitionCache[definition.name]
|
||||||
|
} else {
|
||||||
|
settingsDefinition = getSettingsDefinition(definition)
|
||||||
|
SettingsDefinitionCache[definition.name] = settingsDefinition
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the instance settings, and cache them
|
||||||
|
let instanceSettings
|
||||||
|
if (InstanceSettingsCache[instanceKey]) {
|
||||||
|
instanceSettings = InstanceSettingsCache[instanceKey]
|
||||||
|
} else {
|
||||||
|
instanceSettings = getInstanceSettings(instance, settingsDefinition)
|
||||||
|
InstanceSettingsCache[instanceKey] = instanceSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the settings type in the component
|
||||||
|
staticSettings = instanceSettings.staticSettings
|
||||||
|
dynamicSettings = instanceSettings.dynamicSettings
|
||||||
|
nestedSettings = instanceSettings.nestedSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets the component constructor for the specified component
|
// Gets the component constructor for the specified component
|
||||||
|
@ -180,9 +211,6 @@
|
||||||
if (!definition) {
|
if (!definition) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
if (SettingsDefinitionCache[definition.name]) {
|
|
||||||
return SettingsDefinitionCache[definition.name]
|
|
||||||
}
|
|
||||||
let settings = []
|
let settings = []
|
||||||
definition.settings?.forEach(setting => {
|
definition.settings?.forEach(setting => {
|
||||||
if (setting.section) {
|
if (setting.section) {
|
||||||
|
@ -191,18 +219,17 @@
|
||||||
settings.push(setting)
|
settings.push(setting)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
SettingsDefinitionCache[definition] = settings
|
|
||||||
return settings
|
return settings
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates and enriches component settings when raw settings change
|
const getInstanceSettings = (instance, settingsDefinition) => {
|
||||||
const updateSettings = (settings, key, settingsDefinition) => {
|
// Get raw settings
|
||||||
const instanceChanged = key !== lastInstanceKey
|
let settings = {}
|
||||||
if (!instanceChanged) {
|
Object.entries(instance)
|
||||||
return
|
.filter(([name]) => name === "_conditions" || !name.startsWith("_"))
|
||||||
} else {
|
.forEach(([key, value]) => {
|
||||||
lastInstanceKey = key
|
settings[key] = value
|
||||||
}
|
})
|
||||||
|
|
||||||
// Derive static, dynamic and nested settings if the instance changed
|
// Derive static, dynamic and nested settings if the instance changed
|
||||||
let newStaticSettings = { ...settings }
|
let newStaticSettings = { ...settings }
|
||||||
|
@ -217,11 +244,15 @@
|
||||||
|
|
||||||
// This is a non-nested setting, but we need to find out if it is
|
// This is a non-nested setting, but we need to find out if it is
|
||||||
// static or dynamic
|
// static or dynamic
|
||||||
const value = rawSettings[setting.key]
|
const value = settings[setting.key]
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
delete newDynamicSettings[setting.key]
|
delete newDynamicSettings[setting.key]
|
||||||
} else if (typeof value === "string" && value.includes("{{")) {
|
} else if (typeof value === "string" && value.includes("{{")) {
|
||||||
delete newStaticSettings[setting.key]
|
delete newStaticSettings[setting.key]
|
||||||
|
} else if (value[0]?.["##eventHandlerType"] != null) {
|
||||||
|
console.log(value)
|
||||||
|
// Always treat button actions as dynamic
|
||||||
|
delete newStaticSettings[setting.key]
|
||||||
} else if (typeof value === "object") {
|
} else if (typeof value === "object") {
|
||||||
const stringified = JSON.stringify(value)
|
const stringified = JSON.stringify(value)
|
||||||
if (stringified.includes("{{")) {
|
if (stringified.includes("{{")) {
|
||||||
|
@ -235,9 +266,11 @@
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
staticSettings = newStaticSettings
|
return {
|
||||||
dynamicSettings = newDynamicSettings
|
staticSettings: newStaticSettings,
|
||||||
nestedSettings = newNestedSettings
|
dynamicSettings: newDynamicSettings,
|
||||||
|
nestedSettings: newNestedSettings,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enriches any string component props using handlebars
|
// Enriches any string component props using handlebars
|
||||||
|
|
|
@ -283,6 +283,7 @@ export const enrichButtonActions = (actions, context) => {
|
||||||
const handlers = actions.map(def => handlerMap[def["##eventHandlerType"]])
|
const handlers = actions.map(def => handlerMap[def["##eventHandlerType"]])
|
||||||
return async () => {
|
return async () => {
|
||||||
for (let i = 0; i < handlers.length; i++) {
|
for (let i = 0; i < handlers.length; i++) {
|
||||||
|
let start = Date.now()
|
||||||
try {
|
try {
|
||||||
// Skip any non-existent action definitions
|
// Skip any non-existent action definitions
|
||||||
if (!handlers[i]) {
|
if (!handlers[i]) {
|
||||||
|
@ -344,6 +345,7 @@ export const enrichButtonActions = (actions, context) => {
|
||||||
// Stop executing further actions on error
|
// Stop executing further actions on error
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
console.log("action", i, "took", Date.now() - start, "ms")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { enrichDataBindings } from "./enrichDataBinding"
|
import { enrichDataBindings } from "./enrichDataBinding"
|
||||||
import { enrichButtonActions } from "./buttonActions"
|
import { enrichButtonActions } from "./buttonActions"
|
||||||
|
import { decodeJSBinding } from "@budibase/string-templates"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deeply compares 2 props using JSON.stringify.
|
* Deeply compares 2 props using JSON.stringify.
|
||||||
|
@ -43,9 +44,9 @@ export const enrichProps = (props, context) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Handle conditional UI separately after normal settings
|
// Store the original conditions so that we can restore parts of them after
|
||||||
let conditions = normalProps._conditions
|
// enrichment
|
||||||
delete normalProps._conditions
|
let rawConditions = normalProps._conditions
|
||||||
|
|
||||||
// Enrich all props except button actions
|
// Enrich all props except button actions
|
||||||
let enrichedProps = enrichDataBindings(normalProps, totalContext)
|
let enrichedProps = enrichDataBindings(normalProps, totalContext)
|
||||||
|
@ -58,31 +59,51 @@ export const enrichProps = (props, context) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Conditions
|
// Conditions
|
||||||
if (conditions?.length) {
|
if (enrichedProps._conditions?.length) {
|
||||||
let enrichedConditions = []
|
enrichedProps._conditions.forEach((condition, idx) => {
|
||||||
conditions.forEach(condition => {
|
|
||||||
if (condition.setting?.toLowerCase().includes("onclick")) {
|
if (condition.setting?.toLowerCase().includes("onclick")) {
|
||||||
// Copy and remove the setting value from the condition as it needs
|
// Use the original condition action value to enrich it to a button
|
||||||
// enriched separately
|
// action
|
||||||
let toEnrich = { ...condition }
|
condition.settingValue = enrichButtonActions(
|
||||||
delete toEnrich.settingValue
|
rawConditions[idx].settingValue,
|
||||||
|
|
||||||
// Join the condition back together
|
|
||||||
enrichedConditions.push({
|
|
||||||
...enrichDataBindings(toEnrich, totalContext),
|
|
||||||
settingValue: enrichButtonActions(
|
|
||||||
condition.settingValue,
|
|
||||||
totalContext
|
totalContext
|
||||||
),
|
)
|
||||||
rand: Math.random(),
|
|
||||||
})
|
// Since we can't compare functions, we need to assume that conditions
|
||||||
} else {
|
// change after every enrichment
|
||||||
// Normal condition
|
condition.rand = Math.random()
|
||||||
enrichedConditions.push(enrichDataBindings(condition, totalContext))
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
enrichedProps._conditions = enrichedConditions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return enrichedProps
|
return enrichedProps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a props object references a particular context binding.
|
||||||
|
* e.g. if props are { foo: "My name is {{ person.name }}" }, and we search for" +
|
||||||
|
* "person", then this function wil return true - the props do a context key
|
||||||
|
* called "person".
|
||||||
|
* @param props the props object to search
|
||||||
|
* @param bindingKey the key to search for
|
||||||
|
*/
|
||||||
|
export const propsUseBinding = (props, bindingKey) => {
|
||||||
|
if (!Object.keys(props || {}).length) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const string = JSON.stringify(props)
|
||||||
|
const usedInHBS = string.includes(`[${bindingKey}]`)
|
||||||
|
if (usedInHBS) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const jsBindingRegex = new RegExp("{{ js [^}]+ }}", "g")
|
||||||
|
const jsBindings = [...string.matchAll(jsBindingRegex)]
|
||||||
|
for (let jsBinding of jsBindings) {
|
||||||
|
const encoded = jsBinding[0]
|
||||||
|
const js = decodeJSBinding(encoded)
|
||||||
|
if (js?.includes(`$("[${bindingKey}]`)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue