diff --git a/packages/backend-core/src/queue/inMemoryQueue.ts b/packages/backend-core/src/queue/inMemoryQueue.ts index 867388ae07..842d3243bc 100644 --- a/packages/backend-core/src/queue/inMemoryQueue.ts +++ b/packages/backend-core/src/queue/inMemoryQueue.ts @@ -3,6 +3,7 @@ import { newid } from "../utils" import { Queue, QueueOptions, JobOptions } from "./queue" import { helpers } from "@budibase/shared-core" import { Job, JobId, JobInformation } from "bull" +import { cloneDeep } from "lodash" function jobToJobInformation(job: Job): JobInformation { let cron = "" @@ -33,12 +34,13 @@ function jobToJobInformation(job: Job): JobInformation { } } -interface JobMessage extends Partial> { +export interface TestQueueMessage extends Partial> { id: string timestamp: number queue: Queue data: any opts?: JobOptions + manualTrigger?: boolean } /** @@ -47,12 +49,16 @@ interface JobMessage extends Partial> { * internally to register when messages are available to the consumers - in can * support many inputs and many consumers. */ -class InMemoryQueue implements Partial { +export class InMemoryQueue implements Partial> { _name: string _opts?: QueueOptions - _messages: JobMessage[] + _messages: TestQueueMessage[] _queuedJobIds: Set - _emitter: NodeJS.EventEmitter<{ message: [JobMessage]; completed: [Job] }> + _emitter: NodeJS.EventEmitter<{ + message: [TestQueueMessage] + completed: [Job] + removed: [TestQueueMessage] + }> _runCount: number _addCount: number @@ -82,7 +88,15 @@ class InMemoryQueue implements Partial { */ async process(concurrencyOrFunc: number | any, func?: any) { func = typeof concurrencyOrFunc === "number" ? func : concurrencyOrFunc - this._emitter.on("message", async message => { + this._emitter.on("message", async msg => { + const message = cloneDeep(msg) + + // For the purpose of testing, don't trigger cron jobs immediately. + // Require the test to trigger them manually with timestamps. + if (!message.manualTrigger && message.opts?.repeat != null) { + return + } + let resp = func(message) async function retryFunc(fnc: any) { @@ -97,7 +111,7 @@ class InMemoryQueue implements Partial { if (resp.then != null) { try { await retryFunc(resp) - this._emitter.emit("completed", message as Job) + this._emitter.emit("completed", message as Job) } catch (e: any) { console.error(e) } @@ -114,7 +128,6 @@ class InMemoryQueue implements Partial { return this as any } - // simply puts a message to the queue and emits to the queue for processing /** * Simple function to replicate the add message functionality of Bull, putting * a new message on the queue. This then emits an event which will be used to @@ -123,7 +136,13 @@ class InMemoryQueue implements Partial { * a JSON message as this is required by Bull. * @param repeat serves no purpose for the import queue. */ - async add(data: any, opts?: JobOptions) { + async add(data: T | string, optsOrT?: JobOptions | T) { + if (typeof data === "string") { + throw new Error("doesn't support named jobs") + } + + const opts = optsOrT as JobOptions + const jobId = opts?.jobId?.toString() if (jobId && this._queuedJobIds.has(jobId)) { console.log(`Ignoring already queued job ${jobId}`) @@ -138,7 +157,7 @@ class InMemoryQueue implements Partial { } const pushMessage = () => { - const message: JobMessage = { + const message: TestQueueMessage = { id: newid(), timestamp: Date.now(), queue: this as unknown as Queue, @@ -164,13 +183,14 @@ class InMemoryQueue implements Partial { */ async close() {} - /** - * This removes a cron which has been implemented, this is part of Bull API. - * @param cronJobId The cron which is to be removed. - */ - async removeRepeatableByKey(cronJobId: string) { - // TODO: implement for testing - console.log(cronJobId) + async removeRepeatableByKey(id: string) { + for (const [idx, message] of this._messages.entries()) { + if (message.id === id) { + this._messages.splice(idx, 1) + this._emitter.emit("removed", message) + return + } + } } async removeJobs(_pattern: string) { @@ -193,6 +213,16 @@ class InMemoryQueue implements Partial { return null } + manualTrigger(id: JobId) { + for (const message of this._messages) { + if (message.id === id) { + this._emitter.emit("message", { ...message, manualTrigger: true }) + return + } + } + throw new Error(`Job with id ${id} not found`) + } + on(event: string, callback: (...args: any[]) => void): Queue { // @ts-expect-error - this callback can be one of many types this._emitter.on(event, callback) @@ -214,7 +244,9 @@ class InMemoryQueue implements Partial { } async getRepeatableJobs() { - return this._messages.map(job => jobToJobInformation(job as Job)) + return this._messages + .filter(job => job.opts?.repeat != null) + .map(job => jobToJobInformation(job as Job)) } } diff --git a/packages/backend-core/src/queue/index.ts b/packages/backend-core/src/queue/index.ts index b7d565ba13..5603c40513 100644 --- a/packages/backend-core/src/queue/index.ts +++ b/packages/backend-core/src/queue/index.ts @@ -1,2 +1,3 @@ export * from "./queue" export * from "./constants" +export * from "./inMemoryQueue" diff --git a/packages/bbui/src/Form/Core/Multiselect.svelte b/packages/bbui/src/Form/Core/Multiselect.svelte index 4873430fa0..26951c526f 100644 --- a/packages/bbui/src/Form/Core/Multiselect.svelte +++ b/packages/bbui/src/Form/Core/Multiselect.svelte @@ -14,7 +14,7 @@ export let sort = false export let autoWidth = false export let searchTerm = null - export let customPopoverHeight + export let customPopoverHeight = undefined export let open = false export let loading export let onOptionMouseenter = () => {} diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte index 4af1dcc0ee..a10f493de0 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte @@ -386,7 +386,7 @@ editableColumn.relationshipType = RelationshipType.MANY_TO_MANY } else if (editableColumn.type === FieldType.FORMULA) { editableColumn.formulaType = "dynamic" - editableColumn.responseType = field.responseType || FIELDS.STRING.type + editableColumn.responseType = field?.responseType || FIELDS.STRING.type } } diff --git a/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte b/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte index 2acde47539..fbf74d1e9b 100644 --- a/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte +++ b/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte @@ -40,16 +40,19 @@ indentMore, indentLess, } from "@codemirror/commands" + import { setDiagnostics } from "@codemirror/lint" import { Compartment, EditorState } from "@codemirror/state" + import type { Extension } from "@codemirror/state" import { javascript } from "@codemirror/lang-javascript" import { EditorModes } from "./" import { themeStore } from "@/stores/portal" import type { EditorMode } from "@budibase/types" - import type { BindingCompletion } from "@/types" + import type { BindingCompletion, CodeValidator } from "@/types" + import { validateHbsTemplate } from "./validator/hbs" export let label: string | undefined = undefined - // TODO: work out what best type fits this export let completions: BindingCompletion[] = [] + export let validations: CodeValidator | null = null export let mode: EditorMode = EditorModes.Handlebars export let value: string | null = "" export let placeholder: string | null = null @@ -248,7 +251,7 @@ // None of this is reactive, but it never has been, so we just assume most // config flags aren't changed at runtime // TODO: work out type for base - const buildExtensions = (base: any[]) => { + const buildExtensions = (base: Extension[]) => { let complete = [...base] if (autocompleteEnabled) { @@ -340,6 +343,24 @@ return complete } + function validate( + value: string | null, + editor: EditorView | undefined, + mode: EditorMode, + validations: CodeValidator | null + ) { + if (!value || !validations || !editor) { + return + } + + if (mode === EditorModes.Handlebars) { + const diagnostics = validateHbsTemplate(value, validations) + editor.dispatch(setDiagnostics(editor.state, diagnostics)) + } + } + + $: validate(value, editor, mode, validations) + const initEditor = () => { const baseExtensions = buildBaseExtensions() @@ -366,7 +387,6 @@ {/if} -
diff --git a/packages/builder/src/components/common/CodeEditor/index.ts b/packages/builder/src/components/common/CodeEditor/index.ts index 5529484665..924b4e1b53 100644 --- a/packages/builder/src/components/common/CodeEditor/index.ts +++ b/packages/builder/src/components/common/CodeEditor/index.ts @@ -83,6 +83,8 @@ const helpersToCompletion = ( const helper = helpers[helperName] return { label: helperName, + args: helper.args, + requiresBlock: helper.requiresBlock, info: () => buildHelperInfoNode(helper), type: "helper", section: helperSection, @@ -136,9 +138,13 @@ export const hbAutocomplete = ( baseCompletions: BindingCompletionOption[] ): BindingCompletion => { function coreCompletion(context: CompletionContext) { - let bindingStart = context.matchBefore(EditorModes.Handlebars.match) + if (!baseCompletions.length) { + return null + } - let options = baseCompletions || [] + const bindingStart = context.matchBefore(EditorModes.Handlebars.match) + + const options = baseCompletions if (!bindingStart) { return null @@ -149,7 +155,7 @@ export const hbAutocomplete = ( return null } const query = bindingStart.text.replace(match[0], "") - let filtered = bindingFilter(options, query) + const filtered = bindingFilter(options, query) return { from: bindingStart.from + match[0].length, @@ -169,8 +175,12 @@ export const jsAutocomplete = ( baseCompletions: BindingCompletionOption[] ): BindingCompletion => { function coreCompletion(context: CompletionContext) { - let jsBinding = wrappedAutocompleteMatch(context) - let options = baseCompletions || [] + if (!baseCompletions.length) { + return null + } + + const jsBinding = wrappedAutocompleteMatch(context) + const options = baseCompletions if (jsBinding) { // Accommodate spaces @@ -209,6 +219,10 @@ function setAutocomplete( options: BindingCompletionOption[] ): BindingCompletion { return function (context: CompletionContext) { + if (!options.length) { + return null + } + if (wrappedAutocompleteMatch(context)) { return null } diff --git a/packages/builder/src/components/common/CodeEditor/validator/hbs.ts b/packages/builder/src/components/common/CodeEditor/validator/hbs.ts new file mode 100644 index 0000000000..c2b3b464da --- /dev/null +++ b/packages/builder/src/components/common/CodeEditor/validator/hbs.ts @@ -0,0 +1,113 @@ +/* global hbs */ +import Handlebars from "handlebars" +import type { Diagnostic } from "@codemirror/lint" +import { CodeValidator } from "@/types" + +function isMustacheStatement( + node: hbs.AST.Statement +): node is hbs.AST.MustacheStatement { + return node.type === "MustacheStatement" +} + +function isBlockStatement( + node: hbs.AST.Statement +): node is hbs.AST.BlockStatement { + return node.type === "BlockStatement" +} + +function isPathExpression( + node: hbs.AST.Statement +): node is hbs.AST.PathExpression { + return node.type === "PathExpression" +} + +export function validateHbsTemplate( + text: string, + validations: CodeValidator +): Diagnostic[] { + const diagnostics: Diagnostic[] = [] + + try { + const ast = Handlebars.parse(text, {}) + + const lineOffsets: number[] = [] + let offset = 0 + for (const line of text.split("\n")) { + lineOffsets.push(offset) + offset += line.length + 1 // +1 for newline character + } + + function traverseNodes( + nodes: hbs.AST.Statement[], + options?: { + ignoreMissing?: boolean + } + ) { + const ignoreMissing = options?.ignoreMissing || false + nodes.forEach(node => { + if (isMustacheStatement(node) && isPathExpression(node.path)) { + const helperName = node.path.original + + const from = + lineOffsets[node.loc.start.line - 1] + node.loc.start.column + const to = lineOffsets[node.loc.end.line - 1] + node.loc.end.column + + if (!(helperName in validations)) { + if (!ignoreMissing) { + diagnostics.push({ + from, + to, + severity: "warning", + message: `"${helperName}" handler does not exist.`, + }) + } + return + } + + const { arguments: expectedArguments = [], requiresBlock } = + validations[helperName] + + if (requiresBlock && !isBlockStatement(node)) { + diagnostics.push({ + from, + to, + severity: "error", + message: `Helper "${helperName}" requires a body:\n{{#${helperName} ...}} [body] {{/${helperName}}}`, + }) + return + } + + const providedParams = node.params + + if (providedParams.length !== expectedArguments.length) { + diagnostics.push({ + from, + to, + severity: "error", + message: `Helper "${helperName}" expects ${ + expectedArguments.length + } parameters (${expectedArguments.join(", ")}), but got ${ + providedParams.length + }.`, + }) + } + } + + if (isBlockStatement(node)) { + traverseNodes(node.program.body, { ignoreMissing: true }) + } + }) + } + + traverseNodes(ast.body, { ignoreMissing: true }) + } catch (e: any) { + diagnostics.push({ + from: 0, + to: text.length, + severity: "error", + message: `The handlebars code is not valid:\n${e.message}`, + }) + } + + return diagnostics +} diff --git a/packages/builder/src/components/common/CodeEditor/validator/tests/hbs.spec.ts b/packages/builder/src/components/common/CodeEditor/validator/tests/hbs.spec.ts new file mode 100644 index 0000000000..9484c7a4a5 --- /dev/null +++ b/packages/builder/src/components/common/CodeEditor/validator/tests/hbs.spec.ts @@ -0,0 +1,142 @@ +import { validateHbsTemplate } from "../hbs" +import { CodeValidator } from "@/types" + +describe("hbs validator", () => { + it("validate empty strings", () => { + const text = "" + const validators = {} + + const result = validateHbsTemplate(text, validators) + expect(result).toHaveLength(0) + }) + + it("validate strings without hbs expressions", () => { + const text = "first line\nand another one" + const validators = {} + + const result = validateHbsTemplate(text, validators) + expect(result).toHaveLength(0) + }) + + describe("basic expressions", () => { + const validators = { + fieldName: {}, + } + + it("validate valid expressions", () => { + const text = "{{ fieldName }}" + + const result = validateHbsTemplate(text, validators) + expect(result).toHaveLength(0) + }) + + it("does not throw on missing validations", () => { + const text = "{{ anotherFieldName }}" + + const result = validateHbsTemplate(text, validators) + expect(result).toHaveLength(0) + }) + + // Waiting for missing fields validation + it.skip("throws on untrimmed invalid expressions", () => { + const text = " {{ anotherFieldName }}" + + const result = validateHbsTemplate(text, validators) + expect(result).toEqual([ + { + from: 4, + message: `"anotherFieldName" handler does not exist.`, + severity: "warning", + to: 26, + }, + ]) + }) + + // Waiting for missing fields validation + it.skip("throws on invalid expressions between valid lines", () => { + const text = + "literal expression\nthe value is {{ anotherFieldName }}\nanother expression" + + const result = validateHbsTemplate(text, validators) + expect(result).toEqual([ + { + from: 32, + message: `"anotherFieldName" handler does not exist.`, + severity: "warning", + to: 54, + }, + ]) + }) + + describe("expressions with whitespaces", () => { + const validators = { + [`field name`]: {}, + } + + it("validates expressions with whitespaces", () => { + const text = `{{ [field name] }}` + + const result = validateHbsTemplate(text, validators) + expect(result).toHaveLength(0) + }) + + // Waiting for missing fields validation + it.skip("throws if not wrapped between brackets", () => { + const text = `{{ field name }}` + + const result = validateHbsTemplate(text, validators) + expect(result).toEqual([ + { + from: 0, + message: `"field" handler does not exist.`, + severity: "warning", + to: 16, + }, + ]) + }) + }) + }) + + describe("expressions with parameters", () => { + const validators: CodeValidator = { + helperFunction: { + arguments: ["a", "b", "c"], + }, + } + + it("validate valid params", () => { + const text = "{{ helperFunction 1 99 'a' }}" + + const result = validateHbsTemplate(text, validators) + expect(result).toHaveLength(0) + }) + + it("throws on too few params", () => { + const text = "{{ helperFunction 100 }}" + + const result = validateHbsTemplate(text, validators) + expect(result).toEqual([ + { + from: 0, + message: `Helper "helperFunction" expects 3 parameters (a, b, c), but got 1.`, + severity: "error", + to: 24, + }, + ]) + }) + + it("throws on too many params", () => { + const text = "{{ helperFunction 1 99 'a' 100 }}" + + const result = validateHbsTemplate(text, validators) + expect(result).toEqual([ + { + from: 0, + message: `Helper "helperFunction" expects 3 parameters (a, b, c), but got 4.`, + severity: "error", + to: 34, + }, + ]) + }) + }) +}) diff --git a/packages/builder/src/components/common/bindings/BindingPanel.svelte b/packages/builder/src/components/common/bindings/BindingPanel.svelte index 4bd37bf72c..2c35acdf2d 100644 --- a/packages/builder/src/components/common/bindings/BindingPanel.svelte +++ b/packages/builder/src/components/common/bindings/BindingPanel.svelte @@ -42,7 +42,7 @@ JSONValue, } from "@budibase/types" import type { Log } from "@budibase/string-templates" - import type { BindingCompletion, BindingCompletionOption } from "@/types" + import type { CodeValidator } from "@/types" const dispatch = createEventDispatcher() @@ -58,7 +58,7 @@ export let placeholder = null export let showTabBar = true - let mode: BindingMode | null + let mode: BindingMode let sidePanel: SidePanel | null let initialValueJS = value?.startsWith?.("{{ js ") let jsValue: string | null = initialValueJS ? value : null @@ -88,13 +88,37 @@ | null $: runtimeExpression = readableToRuntimeBinding(enrichedBindings, value) $: requestEval(runtimeExpression, context, snippets) - $: bindingCompletions = bindingsToCompletions(enrichedBindings, editorMode) $: bindingHelpers = new BindingHelpers(getCaretPosition, insertAtPos) - $: hbsCompletions = getHBSCompletions(bindingCompletions) - $: jsCompletions = getJSCompletions(bindingCompletions, snippets, { - useHelpers: allowHelpers, - useSnippets, - }) + + $: bindingOptions = bindingsToCompletions(bindings, editorMode) + $: helperOptions = allowHelpers ? getHelperCompletions(editorMode) : [] + $: snippetsOptions = + usingJS && useSnippets && snippets?.length ? snippets : [] + + $: completions = !usingJS + ? [hbAutocomplete([...bindingOptions, ...helperOptions])] + : [ + jsAutocomplete(bindingOptions), + jsHelperAutocomplete(helperOptions), + snippetAutoComplete(snippetsOptions), + ] + + $: validations = { + ...bindingOptions.reduce((validations, option) => { + validations[option.label] = { + arguments: [], + } + return validations + }, {}), + ...helperOptions.reduce((validations, option) => { + validations[option.label] = { + arguments: option.args, + requiresBlock: option.requiresBlock, + } + return validations + }, {}), + } + $: { // Ensure a valid side panel option is always selected if (sidePanel && !sidePanelOptions.includes(sidePanel)) { @@ -102,38 +126,6 @@ } } - const getHBSCompletions = (bindingCompletions: BindingCompletionOption[]) => { - return [ - hbAutocomplete([ - ...bindingCompletions, - ...getHelperCompletions(EditorModes.Handlebars), - ]), - ] - } - - const getJSCompletions = ( - bindingCompletions: BindingCompletionOption[], - snippets: Snippet[] | null, - config: { - useHelpers: boolean - useSnippets: boolean - } - ) => { - const completions: BindingCompletion[] = [] - if (bindingCompletions.length) { - completions.push(jsAutocomplete([...bindingCompletions])) - } - if (config.useHelpers) { - completions.push( - jsHelperAutocomplete([...getHelperCompletions(EditorModes.JS)]) - ) - } - if (config.useSnippets && snippets) { - completions.push(snippetAutoComplete(snippets)) - } - return completions - } - const getModeOptions = (allowHBS: boolean, allowJS: boolean) => { let options = [] if (allowHBS) { @@ -213,7 +205,7 @@ bindings: EnrichedBinding[], context: any, snippets: Snippet[] | null - ) => { + ): EnrichedBinding[] => { // Create a single big array to enrich in one go const bindingStrings = bindings.map(binding => { if (binding.runtimeBinding.startsWith('trim "')) { @@ -290,7 +282,7 @@ jsValue = null hbsValue = null updateValue(null) - mode = targetMode + mode = targetMode! targetMode = null } @@ -365,13 +357,14 @@ {/if}
{#if mode === BindingMode.Text} - {#key hbsCompletions} + {#key completions} {/key} {:else if mode === BindingMode.JavaScript} - {#key jsCompletions} + {#key completions} 0} + jsBindingWrapping={completions.length > 0} /> {/key} {/if} diff --git a/packages/builder/src/components/design/settings/controls/ButtonConfiguration/ButtonSetting.svelte b/packages/builder/src/components/design/settings/controls/ButtonConfiguration/ButtonSetting.svelte index 726f3cdbea..85b3455172 100644 --- a/packages/builder/src/components/design/settings/controls/ButtonConfiguration/ButtonSetting.svelte +++ b/packages/builder/src/components/design/settings/controls/ButtonConfiguration/ButtonSetting.svelte @@ -43,7 +43,6 @@ import { Icon, Popover, Layout } from "@budibase/bbui" - import { componentStore } from "@/stores/builder" + import { componentStore, selectedScreen } from "@/stores/builder" import { cloneDeep } from "lodash/fp" import { createEventDispatcher, getContext } from "svelte" import ComponentSettingsSection from "@/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte" + import { getComponentBindableProperties } from "@/dataBinding" export let anchor export let componentInstance - export let componentBindings export let bindings export let parseSettings @@ -28,6 +28,10 @@ } $: componentDef = componentStore.getDefinition(componentInstance._component) $: parsedComponentDef = processComponentDefinitionSettings(componentDef) + $: componentBindings = getComponentBindableProperties( + $selectedScreen, + $componentStore.selectedComponentId + ) const open = () => { isOpen = true diff --git a/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldSetting.svelte b/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldSetting.svelte index d39af94ce1..d54fdfccbd 100644 --- a/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldSetting.svelte +++ b/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldSetting.svelte @@ -45,7 +45,6 @@ readableToRuntimeBinding(allBindings, val) - $: isHighlighted = highlightedProp?.key === key - $: highlightType = isHighlighted ? `highlighted-${highlightedProp?.type}` : "" + $: highlightedProp && isHighlighted && scrollToElement(domElement) - const getAllBindings = (bindings, componentBindings, nested) => { - if (!nested) { + const getAllBindings = ( + bindings, + componentBindings, + nested, + contextAccess + ) => { + // contextAccess is a bit of an escape hatch to get around how we render + // certain settings types by using a pseudo component definition, leading + // to problems with the nested flag + if (contextAccess != null) { + // Optionally include global bindings + let allBindings = contextAccess.global ? bindings : [] + + // Optionally include or exclude self (component) bindings. + // If this is a nested setting then we will already have our own context + // bindings mixed in, so if we don't want self context we need to filter + // them out. + if (contextAccess.self) { + return [...allBindings, ...componentBindings] + } else { + return allBindings.filter(binding => { + return !componentBindings.some(componentBinding => { + return componentBinding.runtimeBinding === binding.runtimeBinding + }) + }) + } + } + + // Otherwise just honour the normal nested flag + if (nested) { + return [...bindings, ...componentBindings] + } else { return bindings } - return [...(componentBindings || []), ...(bindings || [])] } // Handle a value change of any type @@ -81,8 +115,6 @@ block: "center", }) } - - $: highlightedProp && isHighlighted && scrollToElement(domElement)
{/if} diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte index 0b147e867c..1d31554df9 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte @@ -151,6 +151,7 @@ propertyFocus={$builderStore.propertyFocus === setting.key} info={setting.info} disableBindings={setting.disableBindings} + contextAccess={setting.contextAccess} props={{ // Generic settings placeholder: setting.placeholder || null, diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ConditionalUIDrawer.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ConditionalUIDrawer.svelte index 863333e91d..a33d60a644 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ConditionalUIDrawer.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ConditionalUIDrawer.svelte @@ -19,6 +19,7 @@ export let conditions = [] export let bindings = [] + export let componentBindings = [] const flipDurationMs = 150 const actionOptions = [ @@ -55,6 +56,7 @@ ] let dragDisabled = true + $: settings = componentStore .getComponentSettings($selectedComponent?._component) ?.concat({ @@ -213,7 +215,10 @@ options: definition.options, placeholder: definition.placeholder, }} + nested={definition.nested} + contextAccess={definition.contextAccess} {bindings} + {componentBindings} /> {:else}