Merge branch 'master' into grid-layout-expansion
This commit is contained in:
commit
fa12abf126
|
@ -70,6 +70,7 @@
|
|||
|
||||
// Stop unnecessary rendering
|
||||
const memoBlock = memo(block)
|
||||
const memoEnvVariables = memo($environment.variables)
|
||||
|
||||
const rowTriggers = [
|
||||
TriggerStepID.ROW_UPDATED,
|
||||
|
@ -91,11 +92,20 @@
|
|||
let insertAtPos, getCaretPosition
|
||||
let stepLayouts = {}
|
||||
|
||||
$: memoEnvVariables.set($environment.variables)
|
||||
$: memoBlock.set(block)
|
||||
|
||||
$: filters = lookForFilters(schemaProperties) || []
|
||||
$: tempFilters = filters
|
||||
$: stepId = block.stepId
|
||||
$: bindings = getAvailableBindings(block, $selectedAutomation?.definition)
|
||||
$: stepId = $memoBlock.stepId
|
||||
|
||||
$: automationBindings = getAvailableBindings(
|
||||
$memoBlock,
|
||||
$selectedAutomation?.definition
|
||||
)
|
||||
$: environmentBindings = buildEnvironmentBindings($memoEnvVariables)
|
||||
$: bindings = [...automationBindings, ...environmentBindings]
|
||||
|
||||
$: getInputData(testData, $memoBlock.inputs)
|
||||
$: tableId = inputData ? inputData.tableId : null
|
||||
$: table = tableId
|
||||
|
@ -110,7 +120,7 @@
|
|||
{ allowLinks: true }
|
||||
)
|
||||
$: queryLimit = tableId?.includes("datasource") ? "∞" : "1000"
|
||||
$: isTrigger = block?.type === AutomationStepType.TRIGGER
|
||||
$: isTrigger = $memoBlock?.type === AutomationStepType.TRIGGER
|
||||
$: codeMode =
|
||||
stepId === AutomationActionStepId.EXECUTE_BASH
|
||||
? EditorModes.Handlebars
|
||||
|
@ -119,13 +129,30 @@
|
|||
disableWrapping: true,
|
||||
})
|
||||
$: editingJs = codeMode === EditorModes.JS
|
||||
$: requiredProperties = isTestModal ? [] : block.schema["inputs"].required
|
||||
$: requiredProperties = isTestModal
|
||||
? []
|
||||
: $memoBlock.schema["inputs"].required
|
||||
|
||||
$: stepCompletions =
|
||||
codeMode === EditorModes.Handlebars
|
||||
? [hbAutocomplete([...bindingsToCompletions(bindings, codeMode)])]
|
||||
: []
|
||||
|
||||
const buildEnvironmentBindings = () => {
|
||||
if ($licensing.environmentVariablesEnabled) {
|
||||
return getEnvironmentBindings().map(binding => {
|
||||
return {
|
||||
...binding,
|
||||
display: {
|
||||
...binding.display,
|
||||
rank: 98,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
const getInputData = (testData, blockInputs) => {
|
||||
// Test data is not cloned for reactivity
|
||||
let newInputData = testData || cloneDeep(blockInputs)
|
||||
|
@ -151,9 +178,9 @@
|
|||
|
||||
// Store for any UX related data
|
||||
const stepStore = writable({})
|
||||
$: currentStep = $stepStore?.[block.id]
|
||||
$: stepState = $stepStore?.[block.id]
|
||||
|
||||
$: customStepLayouts($memoBlock, schemaProperties, currentStep)
|
||||
$: customStepLayouts($memoBlock, schemaProperties, stepState)
|
||||
|
||||
const customStepLayouts = block => {
|
||||
if (
|
||||
|
@ -185,7 +212,6 @@
|
|||
onChange: e => {
|
||||
onChange({ ["revision"]: e.detail })
|
||||
},
|
||||
bindings,
|
||||
updateOnChange: false,
|
||||
forceModal: true,
|
||||
},
|
||||
|
@ -214,7 +240,6 @@
|
|||
onChange: e => {
|
||||
onChange({ [rowIdentifier]: e.detail })
|
||||
},
|
||||
bindings,
|
||||
updateOnChange: false,
|
||||
forceModal: true,
|
||||
},
|
||||
|
@ -275,7 +300,7 @@
|
|||
isUpdateRow: block.stepId === ActionStepID.UPDATE_ROW,
|
||||
}
|
||||
|
||||
if (isTestModal && currentStep?.rowType === "oldRow") {
|
||||
if (isTestModal && stepState?.rowType === "oldRow") {
|
||||
return [
|
||||
{
|
||||
type: RowSelector,
|
||||
|
@ -722,22 +747,9 @@
|
|||
)
|
||||
}
|
||||
|
||||
// Environment bindings
|
||||
if ($licensing.environmentVariablesEnabled) {
|
||||
bindings = bindings.concat(
|
||||
getEnvironmentBindings().map(binding => {
|
||||
return {
|
||||
...binding,
|
||||
display: {
|
||||
...binding.display,
|
||||
rank: 98,
|
||||
},
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
return bindings
|
||||
}
|
||||
|
||||
function lookForFilters(properties) {
|
||||
if (!properties) {
|
||||
return []
|
||||
|
@ -770,7 +782,7 @@
|
|||
drawer.hide()
|
||||
}
|
||||
|
||||
function canShowField(key, value) {
|
||||
function canShowField(value) {
|
||||
const dependsOn = value?.dependsOn
|
||||
return !dependsOn || !!inputData[dependsOn]
|
||||
}
|
||||
|
@ -829,6 +841,7 @@
|
|||
<svelte:component
|
||||
this={config.type}
|
||||
{...config.props}
|
||||
{bindings}
|
||||
on:change={config.props.onChange}
|
||||
/>
|
||||
</PropField>
|
||||
|
@ -836,6 +849,7 @@
|
|||
<svelte:component
|
||||
this={config.type}
|
||||
{...config.props}
|
||||
{bindings}
|
||||
on:change={config.props.onChange}
|
||||
/>
|
||||
{/if}
|
||||
|
|
|
@ -13,6 +13,10 @@
|
|||
import AutomationBindingPanel from "../../common/bindings/ServerBindingPanel.svelte"
|
||||
import CodeEditor from "components/common/CodeEditor/CodeEditor.svelte"
|
||||
import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte"
|
||||
import {
|
||||
readableToRuntimeBinding,
|
||||
runtimeToReadableBinding,
|
||||
} from "dataBinding"
|
||||
|
||||
export let onChange
|
||||
export let field
|
||||
|
@ -30,6 +34,8 @@
|
|||
return clone
|
||||
})
|
||||
|
||||
$: readableValue = runtimeToReadableBinding(parsedBindings, fieldData)
|
||||
|
||||
let attachmentTypes = [
|
||||
FieldType.ATTACHMENTS,
|
||||
FieldType.ATTACHMENT_SINGLE,
|
||||
|
@ -132,11 +138,11 @@
|
|||
/>
|
||||
{:else if schema.type === "longform"}
|
||||
<TextArea
|
||||
value={fieldData}
|
||||
value={readableValue}
|
||||
on:change={e =>
|
||||
onChange({
|
||||
row: {
|
||||
[field]: e.detail,
|
||||
[field]: readableToRuntimeBinding(parsedBindings, e.detail),
|
||||
},
|
||||
})}
|
||||
/>
|
||||
|
@ -144,11 +150,11 @@
|
|||
<span>
|
||||
<div class="field-wrap json-field">
|
||||
<CodeEditor
|
||||
value={fieldData}
|
||||
on:change={e => {
|
||||
value={readableValue}
|
||||
on:blur={e => {
|
||||
onChange({
|
||||
row: {
|
||||
[field]: e.detail,
|
||||
[field]: readableToRuntimeBinding(parsedBindings, e.detail),
|
||||
},
|
||||
})
|
||||
}}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { Label } from "@budibase/bbui"
|
||||
import { onMount, createEventDispatcher } from "svelte"
|
||||
import { onMount, createEventDispatcher, onDestroy } from "svelte"
|
||||
import { FIND_ANY_HBS_REGEX } from "@budibase/string-templates"
|
||||
|
||||
import {
|
||||
|
@ -58,6 +58,64 @@
|
|||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let textarea
|
||||
let editor
|
||||
let mounted = false
|
||||
let isEditorInitialised = false
|
||||
let queuedRefresh = false
|
||||
|
||||
// Theming!
|
||||
let currentTheme = $themeStore?.theme
|
||||
let isDark = !currentTheme.includes("light")
|
||||
let themeConfig = new Compartment()
|
||||
|
||||
$: {
|
||||
if (autofocus && isEditorInitialised) {
|
||||
editor.focus()
|
||||
}
|
||||
}
|
||||
|
||||
// Init when all elements are ready
|
||||
$: if (mounted && !isEditorInitialised) {
|
||||
isEditorInitialised = true
|
||||
initEditor()
|
||||
}
|
||||
|
||||
// Theme change
|
||||
$: if (mounted && isEditorInitialised && $themeStore?.theme) {
|
||||
if (currentTheme != $themeStore?.theme) {
|
||||
currentTheme = $themeStore?.theme
|
||||
isDark = !currentTheme.includes("light")
|
||||
|
||||
// Issue theme compartment update
|
||||
editor.dispatch({
|
||||
effects: themeConfig.reconfigure([...(isDark ? [oneDark] : [])]),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Wait to try and gracefully replace
|
||||
$: refresh(value, isEditorInitialised, mounted)
|
||||
|
||||
/**
|
||||
* Will refresh the editor contents only after
|
||||
* it has been fully initialised
|
||||
* @param value {string} the editor value
|
||||
*/
|
||||
const refresh = (value, initialised, mounted) => {
|
||||
if (!initialised || !mounted) {
|
||||
queuedRefresh = true
|
||||
return
|
||||
}
|
||||
|
||||
if (editor.state.doc.toString() !== value || queuedRefresh) {
|
||||
editor.dispatch({
|
||||
changes: { from: 0, to: editor.state.doc.length, insert: value },
|
||||
})
|
||||
queuedRefresh = false
|
||||
}
|
||||
}
|
||||
|
||||
// Export a function to expose caret position
|
||||
export const getCaretPosition = () => {
|
||||
const selection_range = editor.state.selection.ranges[0]
|
||||
|
@ -132,11 +190,6 @@
|
|||
}
|
||||
)
|
||||
|
||||
// Theming!
|
||||
let currentTheme = $themeStore?.theme
|
||||
let isDark = !currentTheme.includes("light")
|
||||
let themeConfig = new Compartment()
|
||||
|
||||
const indentWithTabCustom = {
|
||||
key: "Tab",
|
||||
run: view => {
|
||||
|
@ -253,6 +306,11 @@
|
|||
lineNumbers(),
|
||||
foldGutter(),
|
||||
keymap.of(buildKeymap()),
|
||||
EditorView.domEventHandlers({
|
||||
blur: () => {
|
||||
dispatch("blur", editor.state.doc.toString())
|
||||
},
|
||||
}),
|
||||
EditorView.updateListener.of(v => {
|
||||
const docStr = v.state.doc?.toString()
|
||||
if (docStr === value) {
|
||||
|
@ -266,11 +324,6 @@
|
|||
return complete
|
||||
}
|
||||
|
||||
let textarea
|
||||
let editor
|
||||
let mounted = false
|
||||
let isEditorInitialised = false
|
||||
|
||||
const initEditor = () => {
|
||||
const baseExtensions = buildBaseExtensions()
|
||||
|
||||
|
@ -281,37 +334,13 @@
|
|||
})
|
||||
}
|
||||
|
||||
$: {
|
||||
if (autofocus && isEditorInitialised) {
|
||||
editor.focus()
|
||||
}
|
||||
}
|
||||
|
||||
// Init when all elements are ready
|
||||
$: if (mounted && !isEditorInitialised) {
|
||||
isEditorInitialised = true
|
||||
initEditor()
|
||||
}
|
||||
|
||||
// Theme change
|
||||
$: if (mounted && isEditorInitialised && $themeStore?.theme) {
|
||||
if (currentTheme != $themeStore?.theme) {
|
||||
currentTheme = $themeStore?.theme
|
||||
isDark = !currentTheme.includes("light")
|
||||
|
||||
// Issue theme compartment update
|
||||
editor.dispatch({
|
||||
effects: themeConfig.reconfigure([...(isDark ? [oneDark] : [])]),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
mounted = true
|
||||
return () => {
|
||||
if (editor) {
|
||||
editor.destroy()
|
||||
}
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
if (editor) {
|
||||
editor.destroy()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
export let allowJS = true
|
||||
export let allowHelpers = true
|
||||
export let updateOnChange = true
|
||||
export let drawerLeft
|
||||
export let type
|
||||
export let schema
|
||||
|
||||
|
@ -170,14 +169,7 @@
|
|||
<Icon disabled={isJS} size="S" name="Close" />
|
||||
</div>
|
||||
{:else}
|
||||
<slot
|
||||
{label}
|
||||
{disabled}
|
||||
readonly={isJS}
|
||||
value={isJS ? "(JavaScript function)" : readableValue}
|
||||
{placeholder}
|
||||
{updateOnChange}
|
||||
/>
|
||||
<slot />
|
||||
{/if}
|
||||
{#if !disabled && type !== "formula" && !disabled && !attachmentTypes.includes(type)}
|
||||
<div
|
||||
|
@ -195,7 +187,7 @@
|
|||
on:drawerShow
|
||||
bind:this={bindingDrawer}
|
||||
title={title ?? placeholder ?? "Bindings"}
|
||||
left={drawerLeft}
|
||||
forceModal={true}
|
||||
>
|
||||
<Button cta slot="buttons" on:click={saveBinding}>Save</Button>
|
||||
<svelte:component
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { getComponentContexts } from "dataBinding"
|
||||
import { getAllComponentContexts } from "dataBinding"
|
||||
import { capitalise } from "helpers"
|
||||
|
||||
// Generates bindings for all components that provider "datasource like"
|
||||
|
@ -7,7 +7,7 @@ import { capitalise } from "helpers"
|
|||
// Some examples are saving rows or duplicating rows.
|
||||
export const getDatasourceLikeProviders = ({ asset, componentId, nested }) => {
|
||||
// Get all form context providers
|
||||
const formComponentContexts = getComponentContexts(
|
||||
const formComponentContexts = getAllComponentContexts(
|
||||
asset,
|
||||
componentId,
|
||||
"form",
|
||||
|
@ -16,7 +16,7 @@ export const getDatasourceLikeProviders = ({ asset, componentId, nested }) => {
|
|||
}
|
||||
)
|
||||
// Get all schema context providers
|
||||
const schemaComponentContexts = getComponentContexts(
|
||||
const schemaComponentContexts = getAllComponentContexts(
|
||||
asset,
|
||||
componentId,
|
||||
"schema",
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
findAllMatchingComponents,
|
||||
findComponent,
|
||||
findComponentPath,
|
||||
getComponentContexts,
|
||||
} from "helpers/components"
|
||||
import {
|
||||
componentStore,
|
||||
|
@ -213,7 +214,7 @@ export const getComponentBindableProperties = (asset, componentId) => {
|
|||
* both global and local bindings, taking into account a component's position
|
||||
* in the component tree.
|
||||
*/
|
||||
export const getComponentContexts = (
|
||||
export const getAllComponentContexts = (
|
||||
asset,
|
||||
componentId,
|
||||
type,
|
||||
|
@ -229,11 +230,6 @@ export const getComponentContexts = (
|
|||
|
||||
// Processes all contexts exposed by a component
|
||||
const processContexts = scope => component => {
|
||||
const def = componentStore.getDefinition(component._component)
|
||||
if (!def?.context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Filter out global contexts not in the same branch.
|
||||
// Global contexts are only valid if their branch root is an ancestor of
|
||||
// this component.
|
||||
|
@ -242,8 +238,8 @@ export const getComponentContexts = (
|
|||
return
|
||||
}
|
||||
|
||||
// Process all contexts provided by this component
|
||||
const contexts = Array.isArray(def.context) ? def.context : [def.context]
|
||||
const componentType = component._component
|
||||
const contexts = getComponentContexts(componentType)
|
||||
contexts.forEach(context => {
|
||||
// Ensure type matches
|
||||
if (type && context.type !== type) {
|
||||
|
@ -261,7 +257,7 @@ export const getComponentContexts = (
|
|||
if (!map[component._id]) {
|
||||
map[component._id] = {
|
||||
component,
|
||||
definition: def,
|
||||
definition: componentStore.getDefinition(componentType),
|
||||
contexts: [],
|
||||
}
|
||||
}
|
||||
|
@ -286,7 +282,7 @@ export const getComponentContexts = (
|
|||
}
|
||||
|
||||
/**
|
||||
* Gets all data provider components above a component.
|
||||
* Gets all components available to this component that expose a certain action
|
||||
*/
|
||||
export const getActionProviders = (
|
||||
asset,
|
||||
|
@ -294,36 +290,30 @@ export const getActionProviders = (
|
|||
actionType,
|
||||
options = { includeSelf: false }
|
||||
) => {
|
||||
if (!asset) {
|
||||
return []
|
||||
}
|
||||
|
||||
// Get all components
|
||||
const components = findAllComponents(asset.props)
|
||||
|
||||
// Find matching contexts and generate bindings
|
||||
let providers = []
|
||||
components.forEach(component => {
|
||||
if (!options?.includeSelf && component._id === componentId) {
|
||||
return
|
||||
}
|
||||
const def = componentStore.getDefinition(component._component)
|
||||
const actions = (def?.actions || []).map(action => {
|
||||
return typeof action === "string" ? { type: action } : action
|
||||
})
|
||||
const action = actions.find(x => x.type === actionType)
|
||||
if (action) {
|
||||
let runtimeBinding = component._id
|
||||
if (action.suffix) {
|
||||
runtimeBinding += `-${action.suffix}`
|
||||
}
|
||||
providers.push({
|
||||
readableBinding: component._instanceName,
|
||||
runtimeBinding,
|
||||
})
|
||||
}
|
||||
const contexts = getAllComponentContexts(asset, componentId, "action", {
|
||||
includeSelf: options?.includeSelf,
|
||||
})
|
||||
return providers
|
||||
return (
|
||||
contexts
|
||||
// Find the definition of the action in question, if one is provided
|
||||
.map(context => ({
|
||||
...context,
|
||||
action: context.contexts[0]?.actions?.find(x => x.type === actionType),
|
||||
}))
|
||||
// Filter out contexts which don't have this action
|
||||
.filter(({ action }) => action != null)
|
||||
// Generate bindings for this component and action
|
||||
.map(({ component, action }) => {
|
||||
let runtimeBinding = component._id
|
||||
if (action.suffix) {
|
||||
runtimeBinding += `-${action.suffix}`
|
||||
}
|
||||
return {
|
||||
readableBinding: component._instanceName,
|
||||
runtimeBinding,
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -371,7 +361,7 @@ export const getDatasourceForProvider = (asset, component) => {
|
|||
*/
|
||||
const getContextBindings = (asset, componentId) => {
|
||||
// Get all available contexts for this component
|
||||
const componentContexts = getComponentContexts(asset, componentId)
|
||||
const componentContexts = getAllComponentContexts(asset, componentId)
|
||||
|
||||
// Generate bindings for each context
|
||||
return componentContexts
|
||||
|
|
|
@ -228,6 +228,25 @@ export const getComponentName = component => {
|
|||
return componentDefinition.friendlyName || componentDefinition.name || ""
|
||||
}
|
||||
|
||||
// Gets all contexts exposed by a certain component type, including actions
|
||||
export const getComponentContexts = component => {
|
||||
const def = componentStore.getDefinition(component)
|
||||
let contexts = []
|
||||
if (def?.context) {
|
||||
contexts = Array.isArray(def.context) ? [...def.context] : [def.context]
|
||||
}
|
||||
if (def?.actions) {
|
||||
contexts.push({
|
||||
type: "action",
|
||||
scope: ContextScopes.Global,
|
||||
|
||||
// Ensure all actions are their verbose object versions
|
||||
actions: def.actions.map(x => (typeof x === "string" ? { type: x } : x)),
|
||||
})
|
||||
}
|
||||
return contexts
|
||||
}
|
||||
|
||||
/**
|
||||
* Recurses through the component tree and builds a tree of contexts provided
|
||||
* by components.
|
||||
|
@ -243,10 +262,9 @@ export const buildContextTree = (
|
|||
}
|
||||
|
||||
// Process this component's contexts
|
||||
const def = componentStore.getDefinition(rootComponent._component)
|
||||
if (def?.context) {
|
||||
const contexts = getComponentContexts(rootComponent._component)
|
||||
if (contexts.length) {
|
||||
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)) {
|
||||
|
|
|
@ -147,6 +147,15 @@
|
|||
onOperatorChange(condition, condition.operator)
|
||||
}
|
||||
}
|
||||
|
||||
const onSettingChange = (e, condition) => {
|
||||
const setting = settings.find(x => x.key === e.detail)
|
||||
if (setting?.defaultValue != null) {
|
||||
condition.settingValue = setting.defaultValue
|
||||
} else {
|
||||
delete condition.settingValue
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
|
@ -189,7 +198,7 @@
|
|||
<Select
|
||||
options={settingOptions}
|
||||
bind:value={condition.setting}
|
||||
on:change={() => delete condition.settingValue}
|
||||
on:change={e => onSettingChange(e, condition)}
|
||||
/>
|
||||
<div>TO</div>
|
||||
{#if definition}
|
||||
|
|
|
@ -2,9 +2,16 @@
|
|||
import { Modal, ModalContent, ProgressBar } from "@budibase/bbui"
|
||||
import { getContext, onMount } from "svelte"
|
||||
import { sleep } from "../../../utils/utils"
|
||||
import { get } from "svelte/store"
|
||||
|
||||
const { clipboard, subscribe, copyAllowed, pasteAllowed, selectedCellCount } =
|
||||
getContext("grid")
|
||||
const {
|
||||
clipboard,
|
||||
subscribe,
|
||||
copyAllowed,
|
||||
pasteAllowed,
|
||||
selectedCellCount,
|
||||
focusedCellAPI,
|
||||
} = getContext("grid")
|
||||
const duration = 260
|
||||
|
||||
let modal
|
||||
|
@ -19,10 +26,15 @@
|
|||
}
|
||||
|
||||
const handlePasteRequest = async () => {
|
||||
// If a cell is active then let the native paste action take over
|
||||
if (get(focusedCellAPI)?.isActive()) {
|
||||
return
|
||||
}
|
||||
progressPercentage = 0
|
||||
if (!$pasteAllowed) {
|
||||
return
|
||||
}
|
||||
|
||||
// Prompt if paste will update multiple cells
|
||||
const multiCellPaste = $selectedCellCount > 1
|
||||
const prompt = $clipboard.multiCellCopy || multiCellPaste
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
case "c":
|
||||
return handle(() => dispatch("copy"))
|
||||
case "v":
|
||||
return handle(() => dispatch("paste"))
|
||||
return dispatch("paste")
|
||||
case "Enter":
|
||||
return handle(() => {
|
||||
if ($config.canAddRows) {
|
||||
|
|
Loading…
Reference in New Issue