1
0
Fork 0
mirror of synced 2024-06-17 09:55:09 +12:00

Add attachment field to forms

This commit is contained in:
Andrew Kingston 2021-01-29 13:22:38 +00:00
parent 6c6542c0c5
commit deccd11def
11 changed files with 160 additions and 71 deletions

View file

@ -14,7 +14,8 @@
"optionsfield",
"booleanfield",
"longformfield",
"datetimefield"
"datetimefield",
"attachmentfield"
]
},
{

View file

@ -0,0 +1,5 @@
<script>
import FormFieldSelect from "./FormFieldSelect.svelte"
</script>
<FormFieldSelect {...$$props} type="attachment" />

View file

@ -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 => {

View file

@ -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"
}

View file

@ -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))
}
}
</script>

View file

@ -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"
}
]
}
}

View file

@ -0,0 +1,27 @@
<script>
import SpectrumField from "./SpectrumField.svelte"
import Dropzone from "../attachments/Dropzone.svelte"
export let field
export let label
let previousFiles = []
let files = []
$: {
// Only actually update the value when it changes, so that we don't trigger
// validation unnecessarily
if (files !== previousFiles) {
fieldApi?.setValue(files)
previousFiles = files
}
}
let fieldState
let fieldApi
</script>
<SpectrumField {label} {field} bind:fieldState bind:fieldApi>
{#if fieldState}
<Dropzone bind:files />
{/if}
</SpectrumField>

View file

@ -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)
</script>
<div
lang="en"
dir="ltr"
use:styleable={$component.styles}
class={`spectrum ${size || 'spectrum--medium'} ${theme || 'spectrum--light'}`}>
{#if loaded}
<slot />
{/if}
</div>
<DataProvider row={{ ...$formState.values, tableId: datasource?.tableId }}>
<div
lang="en"
dir="ltr"
use:styleable={$component.styles}
class={`spectrum ${size || 'spectrum--medium'} ${theme || 'spectrum--light'}`}>
{#if loaded}
<slot />
{/if}
</div>
</DataProvider>

View file

@ -1,17 +1,30 @@
<script>
import { getContext } from "svelte"
const { styleable, builderStore } = getContext("sdk")
const component = getContext("component")
$: styles = {
id: $component.styles.id,
allowSelection: $component.styles.allowSelection,
}
const { builderStore } = getContext("sdk")
</script>
{#if $builderStore.inBuilder}
<div use:styleable={styles}>
<div>
<slot />
</div>
{/if}
<style>
div {
height: var(
--spectrum-alias-item-height-m,
var(--spectrum-global-dimension-size-400)
);
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
color: var(--spectrum-global-color-gray-600);
font-size: var(
--spectrum-alias-item-text-size-m,
var(--spectrum-global-dimension-font-size-100)
);
font-style: var(--spectrum-global-font-style-italic, italic);
font-weight: var(--spectrum-global-font-weight-regular, 400);
}
</style>

View file

@ -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}`
</script>
{#if !fieldState}
<Placeholder>Add the Field setting to start using your component</Placeholder>
{:else if !formContext}
<Placeholder>Form components need to be wrapped in a Form</Placeholder>
{:else}
<FieldGroupFallback>
<div class="spectrum-Form-item" use:styleable={$component.styles}>
<label
for={$fieldState.fieldId}
class={`spectrum-FieldLabel spectrum-FieldLabel--sizeM spectrum-Form-itemLabel ${labelPositionClass}`}>
{label || ''}
</label>
<div class="spectrum-Form-itemField">
<FieldGroupFallback>
<div class="spectrum-Form-item" use:styleable={$component.styles}>
<label
for={$fieldState?.fieldId}
class={`spectrum-FieldLabel spectrum-FieldLabel--sizeM spectrum-Form-itemLabel ${labelPositionClass}`}>
{label || ''}
</label>
<div class="spectrum-Form-itemField">
{#if !formContext}
<Placeholder>Form components need to be wrapped in a Form</Placeholder>
{:else if !fieldState}
<Placeholder>
Add the Field setting to start using your component
</Placeholder>
{:else}
<slot />
{#if $fieldState.error}
<div class="error">{$fieldState.error}</div>
{/if}
</div>
{/if}
</div>
</FieldGroupFallback>
{/if}
</div>
</FieldGroupFallback>
<style>
.error {

View file

@ -6,3 +6,4 @@ export { default as optionsfield } from "./OptionsField.svelte"
export { default as booleanfield } from "./BooleanField.svelte"
export { default as longformfield } from "./LongFormField.svelte"
export { default as datetimefield } from "./DateTimeField.svelte"
export { default as attachmentfield } from "./AttachmentField.svelte"