diff --git a/packages/builder/src/components/design/AppPreview/componentStructure.json b/packages/builder/src/components/design/AppPreview/componentStructure.json
index 6ecdcc8657..53d657d4a2 100644
--- a/packages/builder/src/components/design/AppPreview/componentStructure.json
+++ b/packages/builder/src/components/design/AppPreview/componentStructure.json
@@ -14,7 +14,8 @@
"optionsfield",
"booleanfield",
"longformfield",
- "datetimefield"
+ "datetimefield",
+ "attachmentfield"
]
},
{
diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/AttachmentFieldSelect.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/AttachmentFieldSelect.svelte
new file mode 100644
index 0000000000..9e6ab5529b
--- /dev/null
+++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/AttachmentFieldSelect.svelte
@@ -0,0 +1,5 @@
+
+
+
diff --git a/packages/builder/src/components/design/PropertiesPanel/SettingsView.svelte b/packages/builder/src/components/design/PropertiesPanel/SettingsView.svelte
index a1be940553..bfb97ea293 100644
--- a/packages/builder/src/components/design/PropertiesPanel/SettingsView.svelte
+++ b/packages/builder/src/components/design/PropertiesPanel/SettingsView.svelte
@@ -23,6 +23,7 @@
import BooleanFieldSelect from "./PropertyControls/BooleanFieldSelect.svelte"
import LongFormFieldSelect from "./PropertyControls/LongFormFieldSelect.svelte"
import DateTimeFieldSelect from "./PropertyControls/DateTimeFieldSelect.svelte"
+ import AttachmentFieldSelect from "./PropertyControls/AttachmentFieldSelect.svelte"
export let componentDefinition = {}
export let componentInstance = {}
@@ -70,6 +71,7 @@
"field/boolean": BooleanFieldSelect,
"field/longform": LongFormFieldSelect,
"field/datetime": DateTimeFieldSelect,
+ "field/attachment": AttachmentFieldSelect,
}
const getControl = type => {
diff --git a/packages/client/src/api/api.js b/packages/client/src/api/api.js
index 5509221d5c..d74c5c2a82 100644
--- a/packages/client/src/api/api.js
+++ b/packages/client/src/api/api.js
@@ -20,9 +20,11 @@ const makeApiCall = async ({ method, url, body, json = true }) => {
const requestBody = json ? JSON.stringify(body) : body
let headers = {
Accept: "application/json",
- "Content-Type": "application/json",
"x-budibase-app-id": window["##BUDIBASE_APP_ID##"],
}
+ if (json) {
+ headers["Content-Type"] = "application/json"
+ }
if (!window["##BUDIBASE_IN_BUILDER##"]) {
headers["x-budibase-type"] = "client"
}
diff --git a/packages/client/src/components/Component.svelte b/packages/client/src/components/Component.svelte
index 88db772222..64aa7960ce 100644
--- a/packages/client/src/components/Component.svelte
+++ b/packages/client/src/components/Component.svelte
@@ -9,13 +9,18 @@
export let definition = {}
- let enrichedProps
+ // Props that will be passed to the component instance
let componentProps
// Props are hashed when inside the builder preview and used as a key, so that
// components fully remount whenever any props change
let propsHash = 0
+ // Latest timestamp that we started a props update.
+ // Due to enrichment now being async, we need to avoid overwriting newer
+ // props with old ones, depending on how long enrichment takes.
+ let latestUpdateTime
+
// Get contexts
const dataContext = getContext("data")
@@ -27,8 +32,7 @@
$: constructor = getComponentConstructor(definition._component)
$: children = definition._children || []
$: id = definition._id
- $: enrichComponentProps(definition, $dataContext, $bindingStore)
- $: updateProps(enrichedProps)
+ $: updateComponentProps(definition, $dataContext, $bindingStore)
$: styles = definition._styles
// Update component context
@@ -38,29 +42,6 @@
styles: { ...styles, id },
})
- // Updates the component props.
- // Most props are deeply compared so that svelte will only trigger reactive
- // statements on props that have actually changed.
- const updateProps = props => {
- if (!props) {
- return
- }
- let propsChanged = false
- if (!componentProps) {
- componentProps = {}
- propsChanged = true
- }
- Object.keys(props).forEach(key => {
- if (!propsAreSame(props[key], componentProps[key])) {
- propsChanged = true
- componentProps[key] = props[key]
- }
- })
- if (get(builderStore).inBuilder && propsChanged) {
- propsHash = hashString(JSON.stringify(componentProps))
- }
- }
-
// Gets the component constructor for the specified component
const getComponentConstructor = component => {
const split = component?.split("/")
@@ -72,8 +53,42 @@
}
// Enriches any string component props using handlebars
- const enrichComponentProps = async (definition, context, bindingStore) => {
- enrichedProps = await enrichProps(definition, context, bindingStore)
+ const updateComponentProps = async (definition, context, bindingStore) => {
+ // Record the timestamp so we can reference it after enrichment
+ latestUpdateTime = Date.now()
+ const enrichmentTime = latestUpdateTime
+
+ // Enrich props with context
+ const enrichedProps = await enrichProps(definition, context, bindingStore)
+
+ // Abandon this update if a newer update has started
+ if (enrichmentTime !== latestUpdateTime) {
+ return
+ }
+
+ // Update the component props.
+ // Most props are deeply compared so that svelte will only trigger reactive
+ // statements on props that have actually changed.
+ if (!enrichedProps) {
+ return
+ }
+ let propsChanged = false
+ if (!componentProps) {
+ componentProps = {}
+ propsChanged = true
+ }
+ Object.keys(enrichedProps).forEach(key => {
+ if (!propsAreSame(enrichedProps[key], componentProps[key])) {
+ propsChanged = true
+ componentProps[key] = enrichedProps[key]
+ }
+ })
+
+ // Update the hash if we're in the builder so we can fully remount this
+ // component
+ if (get(builderStore).inBuilder && propsChanged) {
+ propsHash = hashString(JSON.stringify(componentProps))
+ }
}
diff --git a/packages/standard-components/manifest.json b/packages/standard-components/manifest.json
index 353168938a..a0d39b82d4 100644
--- a/packages/standard-components/manifest.json
+++ b/packages/standard-components/manifest.json
@@ -1271,5 +1271,22 @@
"defaultValue": true
}
]
+ },
+ "attachmentfield": {
+ "name": "Attachment",
+ "icon": "ri-calendar-line",
+ "styleable": true,
+ "settings": [
+ {
+ "type": "field/attachment",
+ "label": "Field",
+ "key": "field"
+ },
+ {
+ "type": "text",
+ "label": "Label",
+ "key": "label"
+ }
+ ]
}
}
diff --git a/packages/standard-components/src/forms/AttachmentField.svelte b/packages/standard-components/src/forms/AttachmentField.svelte
new file mode 100644
index 0000000000..ba1945173d
--- /dev/null
+++ b/packages/standard-components/src/forms/AttachmentField.svelte
@@ -0,0 +1,27 @@
+
+
+
+ {#if fieldState}
+
+ {/if}
+
diff --git a/packages/standard-components/src/forms/Form.svelte b/packages/standard-components/src/forms/Form.svelte
index 838a0bc2cf..5edeb10447 100644
--- a/packages/standard-components/src/forms/Form.svelte
+++ b/packages/standard-components/src/forms/Form.svelte
@@ -8,7 +8,7 @@
export let theme
export let size
- const { styleable, API, setBindableValue } = getContext("sdk")
+ const { styleable, API, setBindableValue, DataProvider } = getContext("sdk")
const component = getContext("component")
let loaded = false
@@ -115,12 +115,14 @@
onMount(fetchSchema)
-
- {#if loaded}
-
- {/if}
-
+
+
+ {#if loaded}
+
+ {/if}
+
+
diff --git a/packages/standard-components/src/forms/Placeholder.svelte b/packages/standard-components/src/forms/Placeholder.svelte
index 4c088e6038..517901e7c5 100644
--- a/packages/standard-components/src/forms/Placeholder.svelte
+++ b/packages/standard-components/src/forms/Placeholder.svelte
@@ -1,17 +1,30 @@
{#if $builderStore.inBuilder}
-
+
{/if}
+
+
diff --git a/packages/standard-components/src/forms/SpectrumField.svelte b/packages/standard-components/src/forms/SpectrumField.svelte
index dd144e8518..ca5ddf84db 100644
--- a/packages/standard-components/src/forms/SpectrumField.svelte
+++ b/packages/standard-components/src/forms/SpectrumField.svelte
@@ -16,41 +16,45 @@
const component = getContext("component")
// Register field with form
- const { formApi } = formContext || {}
- const labelPosition = fieldGroupContext?.labelPosition || "above"
- const formField = formApi?.registerField(field)
+ $: formApi = formContext?.formApi
+ $: labelPosition = fieldGroupContext?.labelPosition || "above"
+ $: formField = formApi?.registerField(field)
// Expose field properties to parent component
- fieldState = formField?.fieldState
- fieldApi = formField?.fieldApi
- fieldSchema = formField?.fieldSchema
+ $: {
+ fieldState = formField?.fieldState
+ fieldApi = formField?.fieldApi
+ fieldSchema = formField?.fieldSchema
+ }
// Extract label position from field group context
$: labelPositionClass =
labelPosition === "above" ? "" : `spectrum-FieldLabel--${labelPosition}`
-{#if !fieldState}
-
Add the Field setting to start using your component
-{:else if !formContext}
-
Form components need to be wrapped in a Form
-{:else}
-
-