Add attachment field to forms
This commit is contained in:
parent
acc8b77611
commit
a4de9668ed
|
@ -14,7 +14,8 @@
|
|||
"optionsfield",
|
||||
"booleanfield",
|
||||
"longformfield",
|
||||
"datetimefield"
|
||||
"datetimefield",
|
||||
"attachmentfield"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<script>
|
||||
import FormFieldSelect from "./FormFieldSelect.svelte"
|
||||
</script>
|
||||
|
||||
<FormFieldSelect {...$$props} type="attachment" />
|
|
@ -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 => {
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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,6 +115,7 @@
|
|||
onMount(fetchSchema)
|
||||
</script>
|
||||
|
||||
<DataProvider row={{ ...$formState.values, tableId: datasource?.tableId }}>
|
||||
<div
|
||||
lang="en"
|
||||
dir="ltr"
|
||||
|
@ -124,3 +125,4 @@
|
|||
<slot />
|
||||
{/if}
|
||||
</div>
|
||||
</DataProvider>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
// 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}
|
||||
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}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</FieldGroupFallback>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.error {
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue