From a4de9668ed271f5e9d3e5d808a59828a65d0c4a9 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 29 Jan 2021 13:22:38 +0000 Subject: [PATCH] Add attachment field to forms --- .../design/AppPreview/componentStructure.json | 3 +- .../AttachmentFieldSelect.svelte | 5 ++ .../PropertiesPanel/SettingsView.svelte | 2 + packages/client/src/api/api.js | 4 +- .../client/src/components/Component.svelte | 71 +++++++++++-------- packages/standard-components/manifest.json | 17 +++++ .../src/forms/AttachmentField.svelte | 27 +++++++ .../standard-components/src/forms/Form.svelte | 22 +++--- .../src/forms/Placeholder.svelte | 31 +++++--- .../src/forms/SpectrumField.svelte | 48 +++++++------ .../standard-components/src/forms/index.js | 1 + 11 files changed, 160 insertions(+), 71 deletions(-) create mode 100644 packages/builder/src/components/design/PropertiesPanel/PropertyControls/AttachmentFieldSelect.svelte create mode 100644 packages/standard-components/src/forms/AttachmentField.svelte 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} - -
- -
+ +
+ +
+ {#if !formContext} + Form components need to be wrapped in a Form + {:else if !fieldState} + + Add the Field setting to start using your component + + {:else} {#if $fieldState.error}
{$fieldState.error}
{/if} -
+ {/if}
-
-{/if} +
+