Merge branch 'master' of github.com:budibase/budibase into fix-automation-loop-test-output-2
This commit is contained in:
commit
c8daa99205
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 @@
|
|||
<Label size="S">{label}</Label>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class={`code-editor ${mode?.name || ""}`}>
|
||||
<div tabindex="-1" bind:this={textarea} />
|
||||
</div>
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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<CodeValidator>((validations, option) => {
|
||||
validations[option.label] = {
|
||||
arguments: [],
|
||||
}
|
||||
return validations
|
||||
}, {}),
|
||||
...helperOptions.reduce<CodeValidator>((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}
|
||||
<div class="editor">
|
||||
{#if mode === BindingMode.Text}
|
||||
{#key hbsCompletions}
|
||||
{#key completions}
|
||||
<CodeEditor
|
||||
value={hbsValue}
|
||||
on:change={onChangeHBSValue}
|
||||
bind:getCaretPosition
|
||||
bind:insertAtPos
|
||||
completions={hbsCompletions}
|
||||
{completions}
|
||||
{validations}
|
||||
autofocus={autofocusEditor}
|
||||
placeholder={placeholder ||
|
||||
"Add bindings by typing {{ or use the menu on the right"}
|
||||
|
@ -379,18 +372,18 @@
|
|||
/>
|
||||
{/key}
|
||||
{:else if mode === BindingMode.JavaScript}
|
||||
{#key jsCompletions}
|
||||
{#key completions}
|
||||
<CodeEditor
|
||||
value={jsValue ? decodeJSBinding(jsValue) : jsValue}
|
||||
on:change={onChangeJSValue}
|
||||
completions={jsCompletions}
|
||||
{completions}
|
||||
mode={EditorModes.JS}
|
||||
bind:getCaretPosition
|
||||
bind:insertAtPos
|
||||
autofocus={autofocusEditor}
|
||||
placeholder={placeholder ||
|
||||
"Add bindings by typing $ or use the menu on the right"}
|
||||
jsBindingWrapping={bindingCompletions.length > 0}
|
||||
jsBindingWrapping={completions.length > 0}
|
||||
/>
|
||||
{/key}
|
||||
{/if}
|
||||
|
|
|
@ -43,7 +43,6 @@
|
|||
<EditComponentPopover
|
||||
{anchor}
|
||||
componentInstance={item}
|
||||
{componentBindings}
|
||||
{bindings}
|
||||
on:change
|
||||
parseSettings={updatedNestedFlags}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<script>
|
||||
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
|
||||
|
|
|
@ -45,7 +45,6 @@
|
|||
<EditComponentPopover
|
||||
{anchor}
|
||||
componentInstance={item}
|
||||
{componentBindings}
|
||||
{bindings}
|
||||
{parseSettings}
|
||||
on:change
|
||||
|
|
|
@ -22,25 +22,59 @@
|
|||
export let propertyFocus = false
|
||||
export let info = null
|
||||
export let disableBindings = false
|
||||
export let wide
|
||||
export let wide = false
|
||||
export let contextAccess = null
|
||||
|
||||
let highlightType
|
||||
let domElement
|
||||
|
||||
$: highlightedProp = $builderStore.highlightedSetting
|
||||
$: allBindings = getAllBindings(bindings, componentBindings, nested)
|
||||
$: allBindings = getAllBindings(
|
||||
bindings,
|
||||
componentBindings,
|
||||
nested,
|
||||
contextAccess
|
||||
)
|
||||
$: safeValue = getSafeValue(value, defaultValue, allBindings)
|
||||
$: replaceBindings = val => 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)
|
||||
</script>
|
||||
|
||||
<div
|
||||
|
|
|
@ -147,6 +147,7 @@
|
|||
{componentInstance}
|
||||
{componentDefinition}
|
||||
{bindings}
|
||||
{componentBindings}
|
||||
/>
|
||||
{/if}
|
||||
</Panel>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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}
|
||||
<Select disabled placeholder=" " />
|
||||
|
|
|
@ -64,7 +64,12 @@
|
|||
Show, hide and update components in response to conditions being met.
|
||||
</svelte:fragment>
|
||||
<Button cta slot="buttons" on:click={() => save()}>Save</Button>
|
||||
<ConditionalUIDrawer slot="body" bind:conditions={tempValue} {bindings} />
|
||||
<ConditionalUIDrawer
|
||||
slot="body"
|
||||
bind:conditions={tempValue}
|
||||
{bindings}
|
||||
{componentBindings}
|
||||
/>
|
||||
</Drawer>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -5,4 +5,15 @@ export type BindingCompletion = (context: CompletionContext) => {
|
|||
options: Completion[]
|
||||
} | null
|
||||
|
||||
export type BindingCompletionOption = Completion
|
||||
export interface BindingCompletionOption extends Completion {
|
||||
args?: any[]
|
||||
requiresBlock?: boolean
|
||||
}
|
||||
|
||||
export type CodeValidator = Record<
|
||||
string,
|
||||
{
|
||||
arguments?: any[]
|
||||
requiresBlock?: boolean
|
||||
}
|
||||
>
|
||||
|
|
|
@ -14,5 +14,6 @@
|
|||
"assets/*": ["assets/*"],
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"exclude": []
|
||||
}
|
||||
|
|
|
@ -3089,7 +3089,21 @@
|
|||
{
|
||||
"type": "tableConditions",
|
||||
"label": "Conditions",
|
||||
"key": "conditions"
|
||||
"key": "conditions",
|
||||
"contextAccess": {
|
||||
"global": true,
|
||||
"self": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Format",
|
||||
"key": "format",
|
||||
"info": "Changing format will display values as text",
|
||||
"contextAccess": {
|
||||
"global": false,
|
||||
"self": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -7686,7 +7700,8 @@
|
|||
{
|
||||
"type": "columns/grid",
|
||||
"key": "columns",
|
||||
"resetOn": "table"
|
||||
"resetOn": "table",
|
||||
"nested": true
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
import { get, derived, readable } from "svelte/store"
|
||||
import { featuresStore } from "@/stores"
|
||||
import { Grid } from "@budibase/frontend-core"
|
||||
// import { processStringSync } from "@budibase/string-templates"
|
||||
import { processStringSync } from "@budibase/string-templates"
|
||||
|
||||
// table is actually any datasource, but called table for legacy compatibility
|
||||
export let table
|
||||
|
@ -47,8 +47,8 @@
|
|||
$: currentTheme = $context?.device?.theme
|
||||
$: darkMode = !currentTheme?.includes("light")
|
||||
$: parsedColumns = getParsedColumns(columns)
|
||||
$: schemaOverrides = getSchemaOverrides(parsedColumns)
|
||||
$: enrichedButtons = enrichButtons(buttons)
|
||||
$: schemaOverrides = getSchemaOverrides(parsedColumns, $context)
|
||||
$: selectedRows = deriveSelectedRows(gridContext)
|
||||
$: styles = patchStyles($component.styles, minHeight)
|
||||
$: data = { selectedRows: $selectedRows }
|
||||
|
@ -97,15 +97,19 @@
|
|||
}))
|
||||
}
|
||||
|
||||
const getSchemaOverrides = columns => {
|
||||
const getSchemaOverrides = (columns, context) => {
|
||||
let overrides = {}
|
||||
columns.forEach((column, idx) => {
|
||||
overrides[column.field] = {
|
||||
displayName: column.label,
|
||||
order: idx,
|
||||
conditions: column.conditions,
|
||||
visible: !!column.active,
|
||||
// format: createFormatter(column),
|
||||
conditions: enrichConditions(column.conditions, context),
|
||||
format: createFormatter(column),
|
||||
|
||||
// Small hack to ensure we react to all changes, as our
|
||||
// memoization cannot compare differences in functions
|
||||
rand: column.conditions?.length ? Math.random() : null,
|
||||
}
|
||||
if (column.width) {
|
||||
overrides[column.field].width = column.width
|
||||
|
@ -114,12 +118,24 @@
|
|||
return overrides
|
||||
}
|
||||
|
||||
// const createFormatter = column => {
|
||||
// if (typeof column.format !== "string" || !column.format.trim().length) {
|
||||
// return null
|
||||
// }
|
||||
// return row => processStringSync(column.format, { [id]: row })
|
||||
// }
|
||||
const enrichConditions = (conditions, context) => {
|
||||
return conditions?.map(condition => {
|
||||
return {
|
||||
...condition,
|
||||
referenceValue: processStringSync(
|
||||
condition.referenceValue || "",
|
||||
context
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const createFormatter = column => {
|
||||
if (typeof column.format !== "string" || !column.format.trim().length) {
|
||||
return null
|
||||
}
|
||||
return row => processStringSync(column.format, { [id]: row })
|
||||
}
|
||||
|
||||
const enrichButtons = buttons => {
|
||||
if (!buttons?.length) {
|
||||
|
|
|
@ -31,7 +31,7 @@ export const deriveStores = (context: StoreContext): ConditionDerivedStore => {
|
|||
// Derive and memoize the cell conditions present in our columns so that we
|
||||
// only recompute condition metadata when absolutely necessary
|
||||
const conditions = derivedMemo(columns, $columns => {
|
||||
let newConditions = []
|
||||
let newConditions: UICondition[] = []
|
||||
for (let column of $columns) {
|
||||
for (let condition of column.conditions || []) {
|
||||
newConditions.push({
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
export interface Helper {
|
||||
example: string
|
||||
description: string
|
||||
args: any[]
|
||||
requiresBlock?: boolean
|
||||
}
|
||||
|
|
|
@ -48,6 +48,11 @@ export interface ComponentSetting {
|
|||
selectAllFields?: boolean
|
||||
resetOn?: string | string[]
|
||||
settings?: ComponentSetting[]
|
||||
nested?: boolean
|
||||
dependsOn?: DependsOnComponentSetting
|
||||
sectionDependsOn?: DependsOnComponentSetting
|
||||
contextAccess?: {
|
||||
global: boolean
|
||||
self: boolean
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
import { CalculationType, FieldSchema, FieldType, UIRow } from "@budibase/types"
|
||||
import {
|
||||
CalculationType,
|
||||
FieldSchema,
|
||||
FieldType,
|
||||
UICondition,
|
||||
UIRow,
|
||||
} from "@budibase/types"
|
||||
|
||||
export type UIColumn = FieldSchema & {
|
||||
label: string
|
||||
readonly: boolean
|
||||
conditions: any
|
||||
conditions?: UICondition[]
|
||||
format?: (row: UIRow) => any
|
||||
related?: {
|
||||
field: string
|
||||
|
|
|
@ -3,7 +3,7 @@ import { FieldType, SearchFilter } from "@budibase/types"
|
|||
export interface UICondition {
|
||||
column: string
|
||||
type: FieldType
|
||||
referenceValue: string
|
||||
referenceValue: any
|
||||
operator: SearchFilter["operator"]
|
||||
metadataKey: string
|
||||
metadataValue: string
|
||||
|
|
Loading…
Reference in New Issue