Merge branch 'master' of github.com:budibase/budibase into mongo-tests
This commit is contained in:
commit
0a30fb3364
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.17.2",
|
"version": "2.17.6",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*",
|
"packages/*",
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 52f51dcfb96d3fe58c8cc7a905e7d733f7cd84c2
|
Subproject commit cc12291732ee902dc832bc7d93cf2086ffdf0cff
|
|
@ -7,6 +7,9 @@ import {
|
||||||
findHBSBlocks,
|
findHBSBlocks,
|
||||||
} from "@budibase/string-templates"
|
} from "@budibase/string-templates"
|
||||||
import { capitalise } from "helpers"
|
import { capitalise } from "helpers"
|
||||||
|
import { Constants } from "@budibase/frontend-core"
|
||||||
|
|
||||||
|
const { ContextScopes } = Constants
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively searches for a specific component ID
|
* Recursively searches for a specific component ID
|
||||||
|
@ -263,11 +266,59 @@ export const getComponentName = component => {
|
||||||
if (component == null) {
|
if (component == null) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
const components = get(store)?.components || {}
|
const components = get(store)?.components || {}
|
||||||
const componentDefinition = components[component._component] || {}
|
const componentDefinition = components[component._component] || {}
|
||||||
const name =
|
return componentDefinition.friendlyName || componentDefinition.name || ""
|
||||||
componentDefinition.friendlyName || componentDefinition.name || ""
|
}
|
||||||
|
|
||||||
return 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 context 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
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import {
|
import {
|
||||||
|
buildContextTreeLookupMap,
|
||||||
findAllComponents,
|
findAllComponents,
|
||||||
findAllMatchingComponents,
|
findAllMatchingComponents,
|
||||||
findComponent,
|
findComponent,
|
||||||
|
@ -20,11 +21,13 @@ import {
|
||||||
encodeJSBinding,
|
encodeJSBinding,
|
||||||
} from "@budibase/string-templates"
|
} from "@budibase/string-templates"
|
||||||
import { TableNames } from "../constants"
|
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 ActionDefinitions from "components/design/settings/controls/ButtonActionEditor/manifest.json"
|
||||||
import { environment, licensing } from "stores/portal"
|
import { environment, licensing } from "stores/portal"
|
||||||
import { convertOldFieldFormat } from "components/design/settings/controls/FieldConfiguration/utils"
|
import { convertOldFieldFormat } from "components/design/settings/controls/FieldConfiguration/utils"
|
||||||
|
|
||||||
|
const { ContextScopes } = Constants
|
||||||
|
|
||||||
// Regex to match all instances of template strings
|
// Regex to match all instances of template strings
|
||||||
const CAPTURE_VAR_INSIDE_TEMPLATE = /{{([^}]+)}}/g
|
const CAPTURE_VAR_INSIDE_TEMPLATE = /{{([^}]+)}}/g
|
||||||
const CAPTURE_VAR_INSIDE_JS = /\$\("([^")]+)"\)/g
|
const CAPTURE_VAR_INSIDE_JS = /\$\("([^")]+)"\)/g
|
||||||
|
@ -214,20 +217,27 @@ export const getComponentContexts = (
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
let map = {}
|
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
|
// Processes all contexts exposed by a component
|
||||||
const processContexts = scope => 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) {
|
if (!def?.context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!map[component._id]) {
|
|
||||||
map[component._id] = {
|
// Filter out global contexts not in the same branch.
|
||||||
component,
|
// Global contexts are only valid if their branch root is an ancestor of
|
||||||
definition: def,
|
// this component.
|
||||||
contexts: [],
|
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]
|
const contexts = Array.isArray(def.context) ? def.context : [def.context]
|
||||||
contexts.forEach(context => {
|
contexts.forEach(context => {
|
||||||
// Ensure type matches
|
// Ensure type matches
|
||||||
|
@ -235,7 +245,7 @@ export const getComponentContexts = (
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Ensure scope matches
|
// Ensure scope matches
|
||||||
let contextScope = context.scope || "global"
|
let contextScope = context.scope || ContextScopes.Global
|
||||||
if (contextScope !== scope) {
|
if (contextScope !== scope) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -243,17 +253,23 @@ export const getComponentContexts = (
|
||||||
if (!isContextCompatibleWithComponent(context, component)) {
|
if (!isContextCompatibleWithComponent(context, component)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (!map[component._id]) {
|
||||||
|
map[component._id] = {
|
||||||
|
component,
|
||||||
|
definition: def,
|
||||||
|
contexts: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
map[component._id].contexts.push(context)
|
map[component._id].contexts.push(context)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process all global contexts
|
// Process all global contexts
|
||||||
const allComponents = findAllComponents(asset.props)
|
const allComponents = findAllComponents(asset.props)
|
||||||
allComponents.forEach(processContexts("global"))
|
allComponents.forEach(processContexts(ContextScopes.Global))
|
||||||
|
|
||||||
// Process all local contexts
|
// Process all local contexts in the immediate tree
|
||||||
const localComponents = findComponentPath(asset.props, componentId)
|
componentPath.forEach(processContexts(ContextScopes.Local))
|
||||||
localComponents.forEach(processContexts("local"))
|
|
||||||
|
|
||||||
// Exclude self if required
|
// Exclude self if required
|
||||||
if (!options?.includeSelf) {
|
if (!options?.includeSelf) {
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
Icon,
|
Icon,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
DatePicker,
|
DatePicker,
|
||||||
Detail,
|
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte"
|
import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte"
|
||||||
import { automationStore, selectedAutomation } from "builderStore"
|
import { automationStore, selectedAutomation } from "builderStore"
|
||||||
|
@ -33,6 +32,8 @@
|
||||||
import Editor from "components/integration/QueryEditor.svelte"
|
import Editor from "components/integration/QueryEditor.svelte"
|
||||||
import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte"
|
import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte"
|
||||||
import CodeEditor from "components/common/CodeEditor/CodeEditor.svelte"
|
import CodeEditor from "components/common/CodeEditor/CodeEditor.svelte"
|
||||||
|
import BindingPicker from "components/common/bindings/BindingPicker.svelte"
|
||||||
|
import { BindingHelpers } from "components/common/bindings/utils"
|
||||||
import {
|
import {
|
||||||
bindingsToCompletions,
|
bindingsToCompletions,
|
||||||
hbAutocomplete,
|
hbAutocomplete,
|
||||||
|
@ -56,7 +57,7 @@
|
||||||
let drawer
|
let drawer
|
||||||
let fillWidth = true
|
let fillWidth = true
|
||||||
let inputData
|
let inputData
|
||||||
let codeBindingOpen = false
|
let insertAtPos, getCaretPosition
|
||||||
$: filters = lookForFilters(schemaProperties) || []
|
$: filters = lookForFilters(schemaProperties) || []
|
||||||
$: tempFilters = filters
|
$: tempFilters = filters
|
||||||
$: stepId = block.stepId
|
$: stepId = block.stepId
|
||||||
|
@ -75,6 +76,10 @@
|
||||||
$: isUpdateRow = stepId === ActionStepID.UPDATE_ROW
|
$: isUpdateRow = stepId === ActionStepID.UPDATE_ROW
|
||||||
$: codeMode =
|
$: codeMode =
|
||||||
stepId === "EXECUTE_BASH" ? EditorModes.Handlebars : EditorModes.JS
|
stepId === "EXECUTE_BASH" ? EditorModes.Handlebars : EditorModes.JS
|
||||||
|
$: bindingsHelpers = new BindingHelpers(getCaretPosition, insertAtPos, {
|
||||||
|
disableWrapping: true,
|
||||||
|
})
|
||||||
|
$: editingJs = codeMode === EditorModes.JS
|
||||||
|
|
||||||
$: stepCompletions =
|
$: stepCompletions =
|
||||||
codeMode === EditorModes.Handlebars
|
codeMode === EditorModes.Handlebars
|
||||||
|
@ -539,39 +544,51 @@
|
||||||
/>
|
/>
|
||||||
{:else if value.customType === "code"}
|
{:else if value.customType === "code"}
|
||||||
<CodeEditorModal>
|
<CodeEditorModal>
|
||||||
{#if codeMode == EditorModes.JS}
|
<div class:js-editor={editingJs}>
|
||||||
<ActionButton
|
<div class:js-code={editingJs} style="width: 100%">
|
||||||
on:click={() => (codeBindingOpen = !codeBindingOpen)}
|
<CodeEditor
|
||||||
quiet
|
value={inputData[key]}
|
||||||
icon={codeBindingOpen ? "ChevronDown" : "ChevronRight"}
|
on:change={e => {
|
||||||
>
|
// need to pass without the value inside
|
||||||
<Detail size="S">Bindings</Detail>
|
onChange({ detail: e.detail }, key)
|
||||||
</ActionButton>
|
inputData[key] = e.detail
|
||||||
{#if codeBindingOpen}
|
}}
|
||||||
<pre>{JSON.stringify(bindings, null, 2)}</pre>
|
completions={stepCompletions}
|
||||||
{/if}
|
mode={codeMode}
|
||||||
{/if}
|
autocompleteEnabled={codeMode !== EditorModes.JS}
|
||||||
<CodeEditor
|
bind:getCaretPosition
|
||||||
value={inputData[key]}
|
bind:insertAtPos
|
||||||
on:change={e => {
|
height={500}
|
||||||
// need to pass without the value inside
|
/>
|
||||||
onChange({ detail: e.detail }, key)
|
<div class="messaging">
|
||||||
inputData[key] = e.detail
|
{#if codeMode === EditorModes.Handlebars}
|
||||||
}}
|
<Icon name="FlashOn" />
|
||||||
completions={stepCompletions}
|
<div class="messaging-wrap">
|
||||||
mode={codeMode}
|
<div>
|
||||||
autocompleteEnabled={codeMode != EditorModes.JS}
|
Add available bindings by typing <strong>
|
||||||
height={500}
|
}}
|
||||||
/>
|
</strong>
|
||||||
<div class="messaging">
|
</div>
|
||||||
{#if codeMode == EditorModes.Handlebars}
|
</div>
|
||||||
<Icon name="FlashOn" />
|
{/if}
|
||||||
<div class="messaging-wrap">
|
</div>
|
||||||
<div>
|
</div>
|
||||||
Add available bindings by typing <strong>
|
{#if editingJs}
|
||||||
}}
|
<div class="js-binding-picker">
|
||||||
</strong>
|
<BindingPicker
|
||||||
</div>
|
{bindings}
|
||||||
|
allowHelpers={false}
|
||||||
|
addBinding={binding =>
|
||||||
|
bindingsHelpers.onSelectBinding(
|
||||||
|
inputData[key],
|
||||||
|
binding,
|
||||||
|
{
|
||||||
|
js: true,
|
||||||
|
dontDecode: true,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
mode="javascript"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -658,4 +675,20 @@
|
||||||
.test :global(.drawer) {
|
.test :global(.drawer) {
|
||||||
width: 10000px !important;
|
width: 10000px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.js-editor {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-grow: 1;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-code {
|
||||||
|
flex: 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-binding-picker {
|
||||||
|
flex: 3;
|
||||||
|
margin-top: calc((var(--spacing-xl) * -1) + 1px);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -54,6 +54,7 @@
|
||||||
export let placeholder = null
|
export let placeholder = null
|
||||||
export let autocompleteEnabled = true
|
export let autocompleteEnabled = true
|
||||||
export let autofocus = false
|
export let autofocus = false
|
||||||
|
export let jsBindingWrapping = true
|
||||||
|
|
||||||
// Export a function to expose caret position
|
// Export a function to expose caret position
|
||||||
export const getCaretPosition = () => {
|
export const getCaretPosition = () => {
|
||||||
|
@ -187,7 +188,7 @@
|
||||||
)
|
)
|
||||||
complete.push(
|
complete.push(
|
||||||
EditorView.inputHandler.of((view, from, to, insert) => {
|
EditorView.inputHandler.of((view, from, to, insert) => {
|
||||||
if (insert === "$") {
|
if (jsBindingWrapping && insert === "$") {
|
||||||
let { text } = view.state.doc.lineAt(from)
|
let { text } = view.state.doc.lineAt(from)
|
||||||
|
|
||||||
const left = from ? text.substring(0, from) : ""
|
const left = from ? text.substring(0, from) : ""
|
||||||
|
|
|
@ -286,13 +286,14 @@ export const hbInsert = (value, from, to, text) => {
|
||||||
return parsedInsert
|
return parsedInsert
|
||||||
}
|
}
|
||||||
|
|
||||||
export function jsInsert(value, from, to, text, { helper } = {}) {
|
export function jsInsert(value, from, to, text, { helper, disableWrapping }) {
|
||||||
let parsedInsert = ""
|
let parsedInsert = ""
|
||||||
|
|
||||||
const left = from ? value.substring(0, from) : ""
|
const left = from ? value.substring(0, from) : ""
|
||||||
const right = to ? value.substring(to) : ""
|
const right = to ? value.substring(to) : ""
|
||||||
|
if (disableWrapping) {
|
||||||
if (helper) {
|
parsedInsert = text
|
||||||
|
} else if (helper) {
|
||||||
parsedInsert = `helpers.${text}()`
|
parsedInsert = `helpers.${text}()`
|
||||||
} else if (!left.includes('$("') || !right.includes('")')) {
|
} else if (!left.includes('$("') || !right.includes('")')) {
|
||||||
parsedInsert = `$("${text}")`
|
parsedInsert = `$("${text}")`
|
||||||
|
|
|
@ -29,10 +29,9 @@
|
||||||
hbAutocomplete,
|
hbAutocomplete,
|
||||||
EditorModes,
|
EditorModes,
|
||||||
bindingsToCompletions,
|
bindingsToCompletions,
|
||||||
hbInsert,
|
|
||||||
jsInsert,
|
|
||||||
} from "../CodeEditor"
|
} from "../CodeEditor"
|
||||||
import BindingPicker from "./BindingPicker.svelte"
|
import BindingPicker from "./BindingPicker.svelte"
|
||||||
|
import { BindingHelpers } from "./utils"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
@ -60,8 +59,10 @@
|
||||||
let targetMode = null
|
let targetMode = null
|
||||||
|
|
||||||
$: usingJS = mode === "JavaScript"
|
$: usingJS = mode === "JavaScript"
|
||||||
$: editorMode = mode == "JavaScript" ? EditorModes.JS : EditorModes.Handlebars
|
$: editorMode =
|
||||||
|
mode === "JavaScript" ? EditorModes.JS : EditorModes.Handlebars
|
||||||
$: bindingCompletions = bindingsToCompletions(bindings, editorMode)
|
$: bindingCompletions = bindingsToCompletions(bindings, editorMode)
|
||||||
|
$: bindingHelpers = new BindingHelpers(getCaretPosition, insertAtPos)
|
||||||
|
|
||||||
const updateValue = val => {
|
const updateValue = val => {
|
||||||
valid = isValid(readableToRuntimeBinding(bindings, val))
|
valid = isValid(readableToRuntimeBinding(bindings, val))
|
||||||
|
@ -70,31 +71,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds a JS/HBS helper to the expression
|
|
||||||
const onSelectHelper = (helper, js) => {
|
const onSelectHelper = (helper, js) => {
|
||||||
const pos = getCaretPosition()
|
bindingHelpers.onSelectHelper(js ? jsValue : hbsValue, helper, { js })
|
||||||
const { start, end } = pos
|
|
||||||
if (js) {
|
|
||||||
let js = decodeJSBinding(jsValue)
|
|
||||||
const insertVal = jsInsert(js, start, end, helper.text, { helper: true })
|
|
||||||
insertAtPos({ start, end, value: insertVal })
|
|
||||||
} else {
|
|
||||||
const insertVal = hbInsert(hbsValue, start, end, helper.text)
|
|
||||||
insertAtPos({ start, end, value: insertVal })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds a data binding to the expression
|
|
||||||
const onSelectBinding = (binding, { forceJS } = {}) => {
|
const onSelectBinding = (binding, { forceJS } = {}) => {
|
||||||
const { start, end } = getCaretPosition()
|
const js = usingJS || forceJS
|
||||||
if (usingJS || forceJS) {
|
bindingHelpers.onSelectBinding(js ? jsValue : hbsValue, binding, { js })
|
||||||
let js = decodeJSBinding(jsValue)
|
|
||||||
const insertVal = jsInsert(js, start, end, binding.readableBinding)
|
|
||||||
insertAtPos({ start, end, value: insertVal })
|
|
||||||
} else {
|
|
||||||
const insertVal = hbInsert(hbsValue, start, end, binding.readableBinding)
|
|
||||||
insertAtPos({ start, end, value: insertVal })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const onChangeMode = e => {
|
const onChangeMode = e => {
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
export let bindings
|
export let bindings
|
||||||
export let mode
|
export let mode
|
||||||
export let allowHelpers
|
export let allowHelpers
|
||||||
|
export let noPaddingTop = false
|
||||||
|
|
||||||
let search = ""
|
let search = ""
|
||||||
let popover
|
let popover
|
||||||
|
|
|
@ -1,38 +1,41 @@
|
||||||
export function addHBSBinding(value, caretPos, binding) {
|
import { decodeJSBinding } from "@budibase/string-templates"
|
||||||
binding = typeof binding === "string" ? binding : binding.path
|
import { hbInsert, jsInsert } from "components/common/CodeEditor"
|
||||||
value = value == null ? "" : value
|
|
||||||
|
|
||||||
const left = caretPos?.start ? value.substring(0, caretPos.start) : ""
|
export class BindingHelpers {
|
||||||
const right = caretPos?.end ? value.substring(caretPos.end) : ""
|
constructor(getCaretPosition, insertAtPos, { disableWrapping } = {}) {
|
||||||
if (!left.includes("{{") || !right.includes("}}")) {
|
this.getCaretPosition = getCaretPosition
|
||||||
binding = `{{ ${binding} }}`
|
this.insertAtPos = insertAtPos
|
||||||
|
this.disableWrapping = disableWrapping
|
||||||
}
|
}
|
||||||
if (caretPos.start) {
|
|
||||||
value =
|
|
||||||
value.substring(0, caretPos.start) +
|
|
||||||
binding +
|
|
||||||
value.substring(caretPos.end, value.length)
|
|
||||||
} else {
|
|
||||||
value += binding
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
export function addJSBinding(value, caretPos, binding, { helper } = {}) {
|
// Adds a JS/HBS helper to the expression
|
||||||
binding = typeof binding === "string" ? binding : binding.path
|
onSelectHelper(value, helper, { js, dontDecode }) {
|
||||||
value = value == null ? "" : value
|
const pos = this.getCaretPosition()
|
||||||
if (!helper) {
|
const { start, end } = pos
|
||||||
binding = `$("${binding}")`
|
if (js) {
|
||||||
} else {
|
const jsVal = dontDecode ? value : decodeJSBinding(value)
|
||||||
binding = `helpers.${binding}()`
|
const insertVal = jsInsert(jsVal, start, end, helper.text, {
|
||||||
|
helper: true,
|
||||||
|
})
|
||||||
|
this.insertAtPos({ start, end, value: insertVal })
|
||||||
|
} else {
|
||||||
|
const insertVal = hbInsert(value, start, end, helper.text)
|
||||||
|
this.insertAtPos({ start, end, value: insertVal })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (caretPos.start) {
|
|
||||||
value =
|
// Adds a data binding to the expression
|
||||||
value.substring(0, caretPos.start) +
|
onSelectBinding(value, binding, { js, dontDecode }) {
|
||||||
binding +
|
const { start, end } = this.getCaretPosition()
|
||||||
value.substring(caretPos.end, value.length)
|
if (js) {
|
||||||
} else {
|
const jsVal = dontDecode ? value : decodeJSBinding(value)
|
||||||
value += binding
|
const insertVal = jsInsert(jsVal, start, end, binding.readableBinding, {
|
||||||
|
disableWrapping: this.disableWrapping,
|
||||||
|
})
|
||||||
|
this.insertAtPos({ start, end, value: insertVal })
|
||||||
|
} else {
|
||||||
|
const insertVal = hbInsert(value, start, end, binding.readableBinding)
|
||||||
|
this.insertAtPos({ start, end, value: insertVal })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return value
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4720,7 +4720,8 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"context": {
|
"context": {
|
||||||
"type": "schema"
|
"type": "schema",
|
||||||
|
"scope": "local"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"daterangepicker": {
|
"daterangepicker": {
|
||||||
|
@ -6742,6 +6743,17 @@
|
||||||
"key": "disabled",
|
"key": "disabled",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Read only",
|
||||||
|
"key": "readonly",
|
||||||
|
"defaultValue": false,
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "disabled",
|
||||||
|
"value": true,
|
||||||
|
"invert": true
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"label": "Layout",
|
"label": "Layout",
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import Placeholder from "./Placeholder.svelte"
|
import Placeholder from "./Placeholder.svelte"
|
||||||
import Container from "./Container.svelte"
|
import Container from "./Container.svelte"
|
||||||
import { ContextScopes } from "constants"
|
|
||||||
|
const { Provider, ContextScopes } = getContext("sdk")
|
||||||
|
const component = getContext("component")
|
||||||
|
|
||||||
export let dataProvider
|
export let dataProvider
|
||||||
export let noRowsMessage
|
export let noRowsMessage
|
||||||
|
@ -12,9 +14,6 @@
|
||||||
export let gap
|
export let gap
|
||||||
export let scope = ContextScopes.Local
|
export let scope = ContextScopes.Local
|
||||||
|
|
||||||
const { Provider } = getContext("sdk")
|
|
||||||
const component = getContext("component")
|
|
||||||
|
|
||||||
$: rows = dataProvider?.rows ?? []
|
$: rows = dataProvider?.rows ?? []
|
||||||
$: loaded = dataProvider?.loaded ?? true
|
$: loaded = dataProvider?.loaded ?? true
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
|
|
||||||
export let row
|
export let row
|
||||||
|
|
||||||
const { Provider } = getContext("sdk")
|
const { Provider, ContextScopes } = getContext("sdk")
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Provider data={row}>
|
<Provider data={row} scope={ContextScopes.Local}>
|
||||||
<slot />
|
<slot />
|
||||||
</Provider>
|
</Provider>
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext, setContext, onDestroy } from "svelte"
|
import { getContext, setContext, onDestroy } from "svelte"
|
||||||
import { dataSourceStore, createContextStore } from "stores"
|
import { dataSourceStore, createContextStore } from "stores"
|
||||||
import { ActionTypes, ContextScopes } from "constants"
|
import { ActionTypes } from "constants"
|
||||||
import { generate } from "shortid"
|
import { generate } from "shortid"
|
||||||
|
|
||||||
|
const { ContextScopes } = getContext("sdk")
|
||||||
|
|
||||||
export let data
|
export let data
|
||||||
export let actions
|
export let actions
|
||||||
export let key
|
export let key
|
||||||
|
@ -33,7 +35,7 @@
|
||||||
const provideData = newData => {
|
const provideData = newData => {
|
||||||
const dataKey = JSON.stringify(newData)
|
const dataKey = JSON.stringify(newData)
|
||||||
if (dataKey !== lastDataKey) {
|
if (dataKey !== lastDataKey) {
|
||||||
context.actions.provideData(providerKey, newData, scope)
|
context.actions.provideData(providerKey, newData)
|
||||||
lastDataKey = dataKey
|
lastDataKey = dataKey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,7 +45,7 @@
|
||||||
if (actionsKey !== lastActionsKey) {
|
if (actionsKey !== lastActionsKey) {
|
||||||
lastActionsKey = actionsKey
|
lastActionsKey = actionsKey
|
||||||
newActions?.forEach(({ type, callback, metadata }) => {
|
newActions?.forEach(({ type, callback, metadata }) => {
|
||||||
context.actions.provideAction(providerKey, type, callback, scope)
|
context.actions.provideAction(providerKey, type, callback)
|
||||||
|
|
||||||
// Register any "refresh datasource" actions with a singleton store
|
// Register any "refresh datasource" actions with a singleton store
|
||||||
// so we can easily refresh data at all levels for any datasource
|
// so we can easily refresh data at all levels for any datasource
|
||||||
|
|
|
@ -12,10 +12,5 @@ export const ActionTypes = {
|
||||||
ScrollTo: "ScrollTo",
|
ScrollTo: "ScrollTo",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ContextScopes = {
|
|
||||||
Local: "local",
|
|
||||||
Global: "global",
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DNDPlaceholderID = "dnd-placeholder"
|
export const DNDPlaceholderID = "dnd-placeholder"
|
||||||
export const ScreenslotType = "screenslot"
|
export const ScreenslotType = "screenslot"
|
||||||
|
|
|
@ -23,12 +23,12 @@ import { getAction } from "utils/getAction"
|
||||||
import Provider from "components/context/Provider.svelte"
|
import Provider from "components/context/Provider.svelte"
|
||||||
import Block from "components/Block.svelte"
|
import Block from "components/Block.svelte"
|
||||||
import BlockComponent from "components/BlockComponent.svelte"
|
import BlockComponent from "components/BlockComponent.svelte"
|
||||||
import { ActionTypes, ContextScopes } from "./constants"
|
import { ActionTypes } from "./constants"
|
||||||
import { fetchDatasourceSchema } from "./utils/schema.js"
|
import { fetchDatasourceSchema } from "./utils/schema.js"
|
||||||
import { getAPIKey } from "./utils/api.js"
|
import { getAPIKey } from "./utils/api.js"
|
||||||
import { enrichButtonActions } from "./utils/buttonActions.js"
|
import { enrichButtonActions } from "./utils/buttonActions.js"
|
||||||
import { processStringSync, makePropSafe } from "@budibase/string-templates"
|
import { processStringSync, makePropSafe } from "@budibase/string-templates"
|
||||||
import { fetchData, LuceneUtils } from "@budibase/frontend-core"
|
import { fetchData, LuceneUtils, Constants } from "@budibase/frontend-core"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
API,
|
API,
|
||||||
|
@ -57,7 +57,7 @@ export default {
|
||||||
fetchDatasourceSchema,
|
fetchDatasourceSchema,
|
||||||
fetchData,
|
fetchData,
|
||||||
LuceneUtils,
|
LuceneUtils,
|
||||||
ContextScopes,
|
ContextScopes: Constants.ContextScopes,
|
||||||
getAPIKey,
|
getAPIKey,
|
||||||
enrichButtonActions,
|
enrichButtonActions,
|
||||||
processStringSync,
|
processStringSync,
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { writable, derived } from "svelte/store"
|
import { writable, derived } from "svelte/store"
|
||||||
import { ContextScopes } from "constants"
|
|
||||||
|
|
||||||
export const createContextStore = parentContext => {
|
export const createContextStore = parentContext => {
|
||||||
const context = writable({})
|
const context = writable({})
|
||||||
|
@ -20,60 +19,34 @@ export const createContextStore = parentContext => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provide some data in context
|
// Provide some data in context
|
||||||
const provideData = (providerId, data, scope = ContextScopes.Global) => {
|
const provideData = (providerId, data) => {
|
||||||
if (!providerId || data === undefined) {
|
if (!providerId || data === undefined) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Proxy message up the chain if we have a parent and are providing global
|
|
||||||
// context
|
|
||||||
if (scope === ContextScopes.Global && parentContext) {
|
|
||||||
parentContext.actions.provideData(providerId, data, scope)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise this is either the context root, or we're providing a local
|
// Otherwise this is either the context root, or we're providing a local
|
||||||
// context override, so we need to update the local context instead
|
// context override, so we need to update the local context instead
|
||||||
else {
|
context.update(state => {
|
||||||
context.update(state => {
|
state[providerId] = data
|
||||||
state[providerId] = data
|
return state
|
||||||
return state
|
})
|
||||||
})
|
broadcastChange(providerId)
|
||||||
broadcastChange(providerId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provides some action in context
|
// Provides some action in context
|
||||||
const provideAction = (
|
const provideAction = (providerId, actionType, callback) => {
|
||||||
providerId,
|
|
||||||
actionType,
|
|
||||||
callback,
|
|
||||||
scope = ContextScopes.Global
|
|
||||||
) => {
|
|
||||||
if (!providerId || !actionType) {
|
if (!providerId || !actionType) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Proxy message up the chain if we have a parent and are providing global
|
|
||||||
// context
|
|
||||||
if (scope === ContextScopes.Global && parentContext) {
|
|
||||||
parentContext.actions.provideAction(
|
|
||||||
providerId,
|
|
||||||
actionType,
|
|
||||||
callback,
|
|
||||||
scope
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise this is either the context root, or we're providing a local
|
// Otherwise this is either the context root, or we're providing a local
|
||||||
// context override, so we need to update the local context instead
|
// context override, so we need to update the local context instead
|
||||||
else {
|
const key = `${providerId}_${actionType}`
|
||||||
const key = `${providerId}_${actionType}`
|
context.update(state => {
|
||||||
context.update(state => {
|
state[key] = callback
|
||||||
state[key] = callback
|
return state
|
||||||
return state
|
})
|
||||||
})
|
broadcastChange(key)
|
||||||
broadcastChange(key)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const observeChanges = callback => {
|
const observeChanges = callback => {
|
||||||
|
|
|
@ -106,3 +106,8 @@ export const Themes = [
|
||||||
export const EventPublishType = {
|
export const EventPublishType = {
|
||||||
ENV_VAR_UPGRADE_PANEL_OPENED: "environment_variable_upgrade_panel_opened",
|
ENV_VAR_UPGRADE_PANEL_OPENED: "environment_variable_upgrade_panel_opened",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ContextScopes = {
|
||||||
|
Local: "local",
|
||||||
|
Global: "global",
|
||||||
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 4f9616f163039a0eea81319d8e2288340a2ebc79
|
Subproject commit aaf7101cd1493215155cc8f83124c70d53eb1be4
|
|
@ -1,4 +1,4 @@
|
||||||
import fetch from "node-fetch"
|
import { Response, default as fetch } from "node-fetch"
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import { checkSlashesInUrl } from "./index"
|
import { checkSlashesInUrl } from "./index"
|
||||||
import {
|
import {
|
||||||
|
@ -40,25 +40,21 @@ export function request(ctx?: Ctx, request?: any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkResponse(
|
async function checkResponse(
|
||||||
response: any,
|
response: Response,
|
||||||
errorMsg: string,
|
errorMsg: string,
|
||||||
{ ctx }: { ctx?: Ctx } = {}
|
{ ctx }: { ctx?: Ctx } = {}
|
||||||
) {
|
) {
|
||||||
if (response.status !== 200) {
|
if (response.status >= 300) {
|
||||||
let error
|
let responseErrorMessage
|
||||||
try {
|
if (response.headers.get("content-type")?.includes("json")) {
|
||||||
error = await response.json()
|
const error = await response.json()
|
||||||
if (!error.message) {
|
responseErrorMessage = error.message ?? JSON.stringify(error)
|
||||||
error = JSON.stringify(error)
|
} else {
|
||||||
}
|
responseErrorMessage = await response.text()
|
||||||
} catch (err) {
|
|
||||||
error = await response.text()
|
|
||||||
}
|
}
|
||||||
const msg = `Unable to ${errorMsg} - ${
|
const msg = `Unable to ${errorMsg} - ${responseErrorMessage}`
|
||||||
error.message ? error.message : error
|
|
||||||
}`
|
|
||||||
if (ctx) {
|
if (ctx) {
|
||||||
ctx.throw(400, msg)
|
ctx.throw(msg, response.status)
|
||||||
} else {
|
} else {
|
||||||
throw msg
|
throw msg
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,7 @@
|
||||||
"koa-useragent": "^4.1.0",
|
"koa-useragent": "^4.1.0",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"node-fetch": "2.6.7",
|
"node-fetch": "2.6.7",
|
||||||
"nodemailer": "6.7.2",
|
"nodemailer": "6.9.9",
|
||||||
"passport-google-oauth": "2.0.0",
|
"passport-google-oauth": "2.0.0",
|
||||||
"passport-local": "1.0.0",
|
"passport-local": "1.0.0",
|
||||||
"pouchdb": "7.3.0",
|
"pouchdb": "7.3.0",
|
||||||
|
|
Loading…
Reference in New Issue