diff --git a/packages/builder/src/builderStore/componentUtils.js b/packages/builder/src/builderStore/componentUtils.js index 733ce0948e..8a7cd93d0f 100644 --- a/packages/builder/src/builderStore/componentUtils.js +++ b/packages/builder/src/builderStore/componentUtils.js @@ -7,6 +7,9 @@ import { findHBSBlocks, } from "@budibase/string-templates" import { capitalise } from "helpers" +import { Constants } from "@budibase/frontend-core" + +const { ContextScopes } = Constants /** * Recursively searches for a specific component ID @@ -263,11 +266,59 @@ export const getComponentName = component => { if (component == null) { return "" } - const components = get(store)?.components || {} const componentDefinition = components[component._component] || {} - const name = - componentDefinition.friendlyName || componentDefinition.name || "" - - return name + return componentDefinition.friendlyName || componentDefinition.name || "" +} + +/** + * Recurses through the component tree and builds a tree of contexts provided + * by components. + */ +export const buildContextTree = ( + rootComponent, + tree = { root: [] }, + currentBranch = "root" +) => { + // Sanity check + if (!rootComponent) { + return tree + } + + // Process this component's contexts + const def = store.actions.components.getDefinition(rootComponent._component) + if (def?.context) { + tree[currentBranch].push(rootComponent._id) + const contexts = Array.isArray(def.context) ? def.context : [def.context] + + // If we provide local context, start a new branch for our children + if (contexts.some(context => context.scope === ContextScopes.Local)) { + currentBranch = rootComponent._id + tree[rootComponent._id] = [] + } + } + + // Process children + if (rootComponent._children) { + rootComponent._children.forEach(child => { + buildContextTree(child, tree, currentBranch) + }) + } + + return tree +} + +/** + * Generates a lookup map of which content branch all components in a component + * tree are inside. + */ +export const buildContextTreeLookupMap = rootComponent => { + const tree = buildContextTree(rootComponent) + let map = {} + Object.entries(tree).forEach(([branch, ids]) => { + ids.forEach(id => { + map[id] = branch + }) + }) + return map } diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js index 86aecd466f..951d615e7a 100644 --- a/packages/builder/src/builderStore/dataBinding.js +++ b/packages/builder/src/builderStore/dataBinding.js @@ -1,6 +1,8 @@ import { cloneDeep } from "lodash/fp" import { get } from "svelte/store" import { + buildContextTree, + buildContextTreeLookupMap, findAllComponents, findAllMatchingComponents, findComponent, @@ -20,11 +22,13 @@ import { encodeJSBinding, } from "@budibase/string-templates" import { TableNames } from "../constants" -import { JSONUtils } from "@budibase/frontend-core" +import { JSONUtils, Constants } from "@budibase/frontend-core" import ActionDefinitions from "components/design/settings/controls/ButtonActionEditor/manifest.json" import { environment, licensing } from "stores/portal" import { convertOldFieldFormat } from "components/design/settings/controls/FieldConfiguration/utils" +const { ContextScopes } = Constants + // Regex to match all instances of template strings const CAPTURE_VAR_INSIDE_TEMPLATE = /{{([^}]+)}}/g const CAPTURE_VAR_INSIDE_JS = /\$\("([^")]+)"\)/g @@ -214,20 +218,27 @@ export const getComponentContexts = ( return [] } let map = {} + const componentPath = findComponentPath(asset.props, componentId) + const componentPathIds = componentPath.map(component => component._id) + const contextTreeLookupMap = buildContextTreeLookupMap(asset.props) // Processes all contexts exposed by a component const processContexts = scope => component => { - const def = store.actions.components.getDefinition(component._component) + // Sanity check + const def = store.actions.components.getDefinition(component?._component) if (!def?.context) { return } - if (!map[component._id]) { - map[component._id] = { - component, - definition: def, - contexts: [], - } + + // Filter out global contexts not in the same branch. + // Global contexts are only valid if their branch root is an ancestor of + // this component. + const branch = contextTreeLookupMap[component._id] + if (branch !== "root" && !componentPathIds.includes(branch)) { + return } + + // Process all contexts provided by this component const contexts = Array.isArray(def.context) ? def.context : [def.context] contexts.forEach(context => { // Ensure type matches @@ -235,7 +246,7 @@ export const getComponentContexts = ( return } // Ensure scope matches - let contextScope = context.scope || "global" + let contextScope = context.scope || ContextScopes.Global if (contextScope !== scope) { return } @@ -243,17 +254,23 @@ export const getComponentContexts = ( if (!isContextCompatibleWithComponent(context, component)) { return } + if (!map[component._id]) { + map[component._id] = { + component, + definition: def, + contexts: [], + } + } map[component._id].contexts.push(context) }) } // Process all global contexts const allComponents = findAllComponents(asset.props) - allComponents.forEach(processContexts("global")) + allComponents.forEach(processContexts(ContextScopes.Global)) - // Process all local contexts - const localComponents = findComponentPath(asset.props, componentId) - localComponents.forEach(processContexts("local")) + // Process all local contexts in the immediate tree + componentPath.forEach(processContexts(ContextScopes.Local)) // Exclude self if required if (!options?.includeSelf) { diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 5bbf465766..febc03085e 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -4720,7 +4720,8 @@ } ], "context": { - "type": "schema" + "type": "schema", + "scope": "local" } }, "daterangepicker": { diff --git a/packages/client/src/components/app/Repeater.svelte b/packages/client/src/components/app/Repeater.svelte index 8796078311..2d07342cf5 100644 --- a/packages/client/src/components/app/Repeater.svelte +++ b/packages/client/src/components/app/Repeater.svelte @@ -2,7 +2,9 @@ import { getContext } from "svelte" import Placeholder from "./Placeholder.svelte" import Container from "./Container.svelte" - import { ContextScopes } from "constants" + + const { Provider, ContextScopes } = getContext("sdk") + const component = getContext("component") export let dataProvider export let noRowsMessage @@ -12,9 +14,6 @@ export let gap export let scope = ContextScopes.Local - const { Provider } = getContext("sdk") - const component = getContext("component") - $: rows = dataProvider?.rows ?? [] $: loaded = dataProvider?.loaded ?? true diff --git a/packages/client/src/components/context/Provider.svelte b/packages/client/src/components/context/Provider.svelte index ad5b580c4f..fe4463821e 100644 --- a/packages/client/src/components/context/Provider.svelte +++ b/packages/client/src/components/context/Provider.svelte @@ -1,9 +1,11 @@