Add form block and form block plus components
This commit is contained in:
parent
459c285d23
commit
d08d9d1248
|
@ -47,7 +47,6 @@ filterTests(["smoke", "all"], () => {
|
||||||
cy.readFile(
|
cy.readFile(
|
||||||
"cypress/support/queryLevelTransformerFunctionWithData.js"
|
"cypress/support/queryLevelTransformerFunctionWithData.js"
|
||||||
).then(transformerFunction => {
|
).then(transformerFunction => {
|
||||||
//console.log(transformerFunction[1])
|
|
||||||
cy.get(".CodeMirror textarea")
|
cy.get(".CodeMirror textarea")
|
||||||
// Highlight current text and overwrite with file contents
|
// Highlight current text and overwrite with file contents
|
||||||
.type(Cypress.platform === "darwin" ? "{cmd}a" : "{ctrl}a", {
|
.type(Cypress.platform === "darwin" ? "{cmd}a" : "{ctrl}a", {
|
||||||
|
|
|
@ -65,7 +65,12 @@ export const getComponentBindableProperties = (asset, componentId) => {
|
||||||
/**
|
/**
|
||||||
* Gets all data provider components above a component.
|
* Gets all data provider components above a component.
|
||||||
*/
|
*/
|
||||||
export const getContextProviderComponents = (asset, componentId, type) => {
|
export const getContextProviderComponents = (
|
||||||
|
asset,
|
||||||
|
componentId,
|
||||||
|
type,
|
||||||
|
options = { includeSelf: false }
|
||||||
|
) => {
|
||||||
if (!asset || !componentId) {
|
if (!asset || !componentId) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
@ -73,7 +78,9 @@ export const getContextProviderComponents = (asset, componentId, type) => {
|
||||||
// Get the component tree leading up to this component, ignoring the component
|
// Get the component tree leading up to this component, ignoring the component
|
||||||
// itself
|
// itself
|
||||||
const path = findComponentPath(asset.props, componentId)
|
const path = findComponentPath(asset.props, componentId)
|
||||||
|
if (!options?.includeSelf) {
|
||||||
path.pop()
|
path.pop()
|
||||||
|
}
|
||||||
|
|
||||||
// Filter by only data provider components
|
// Filter by only data provider components
|
||||||
return path.filter(component => {
|
return path.filter(component => {
|
||||||
|
@ -138,20 +145,8 @@ export const getDatasourceForProvider = (asset, component) => {
|
||||||
if (!datasourceSetting) {
|
if (!datasourceSetting) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// There are different types of setting which can be a datasource, for
|
|
||||||
// example an actual datasource object, or a table ID string.
|
|
||||||
// Convert the datasource setting into a proper datasource object so that
|
|
||||||
// we can use it properly
|
|
||||||
if (datasourceSetting.type === "table") {
|
|
||||||
return {
|
|
||||||
tableId: component[datasourceSetting?.key],
|
|
||||||
type: "table",
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return component[datasourceSetting?.key]
|
return component[datasourceSetting?.key]
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets all bindable data properties from component data contexts.
|
* Gets all bindable data properties from component data contexts.
|
||||||
|
@ -643,6 +638,17 @@ const buildFormSchema = component => {
|
||||||
if (!component) {
|
if (!component) {
|
||||||
return schema
|
return schema
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If this is a form block, simply use the fields setting
|
||||||
|
if (component._component.endsWith("formblock")) {
|
||||||
|
let schema = {}
|
||||||
|
component.fields?.forEach(field => {
|
||||||
|
schema[field] = { type: "string" }
|
||||||
|
})
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise find all field component children
|
||||||
const settings = getComponentSettings(component._component)
|
const settings = getComponentSettings(component._component)
|
||||||
const fieldSetting = settings.find(
|
const fieldSetting = settings.find(
|
||||||
setting => setting.key === "field" && setting.type.startsWith("field/")
|
setting => setting.key === "field" && setting.type.startsWith("field/")
|
||||||
|
|
|
@ -5,26 +5,20 @@
|
||||||
"children": [
|
"children": [
|
||||||
"tableblock",
|
"tableblock",
|
||||||
"cardsblock",
|
"cardsblock",
|
||||||
"repeaterblock"
|
"repeaterblock",
|
||||||
|
"formblock",
|
||||||
|
"formblockplus"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Layout",
|
"name": "Layout",
|
||||||
"icon": "ClassicGridView",
|
"icon": "ClassicGridView",
|
||||||
"children": [
|
"children": ["container", "section"]
|
||||||
"container",
|
|
||||||
"section"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Data",
|
"name": "Data",
|
||||||
"icon": "Data",
|
"icon": "Data",
|
||||||
"children": [
|
"children": ["dataprovider", "repeater", "table", "dynamicfilter"]
|
||||||
"dataprovider",
|
|
||||||
"repeater",
|
|
||||||
"table",
|
|
||||||
"dynamicfilter"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Form",
|
"name": "Form",
|
||||||
|
@ -51,22 +45,12 @@
|
||||||
{
|
{
|
||||||
"name": "Card",
|
"name": "Card",
|
||||||
"icon": "Card",
|
"icon": "Card",
|
||||||
"children": [
|
"children": ["spectrumcard", "cardstat"]
|
||||||
"spectrumcard",
|
|
||||||
"cardstat"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Chart",
|
"name": "Chart",
|
||||||
"icon": "GraphBarVertical",
|
"icon": "GraphBarVertical",
|
||||||
"children": [
|
"children": ["bar", "line", "area", "pie", "donut", "candlestick"]
|
||||||
"bar",
|
|
||||||
"line",
|
|
||||||
"area",
|
|
||||||
"pie",
|
|
||||||
"donut",
|
|
||||||
"candlestick"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Elements",
|
"name": "Elements",
|
||||||
|
@ -87,4 +71,3 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,6 @@
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error duplicating screen")
|
notifications.error("Error duplicating screen")
|
||||||
console.log(error)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
export let key
|
export let key
|
||||||
export let actions
|
export let actions
|
||||||
export let bindings = []
|
export let bindings = []
|
||||||
|
export let nested
|
||||||
|
|
||||||
let selectedAction = actions?.length ? actions[0] : null
|
let selectedAction = actions?.length ? actions[0] : null
|
||||||
|
|
||||||
|
@ -137,6 +138,7 @@
|
||||||
this={selectedActionComponent}
|
this={selectedActionComponent}
|
||||||
parameters={selectedAction.parameters}
|
parameters={selectedAction.parameters}
|
||||||
bindings={allBindings}
|
bindings={allBindings}
|
||||||
|
{nested}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/key}
|
{/key}
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
export let value = []
|
export let value = []
|
||||||
export let name
|
export let name
|
||||||
export let bindings
|
export let bindings
|
||||||
|
export let nested
|
||||||
|
|
||||||
let drawer
|
let drawer
|
||||||
let tmpValue
|
let tmpValue
|
||||||
|
@ -83,5 +84,6 @@
|
||||||
eventType={name}
|
eventType={name}
|
||||||
{bindings}
|
{bindings}
|
||||||
{key}
|
{key}
|
||||||
|
{nested}
|
||||||
/>
|
/>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
|
@ -10,11 +10,13 @@
|
||||||
|
|
||||||
export let parameters
|
export let parameters
|
||||||
export let bindings = []
|
export let bindings = []
|
||||||
|
export let nested
|
||||||
|
|
||||||
$: formComponents = getContextProviderComponents(
|
$: formComponents = getContextProviderComponents(
|
||||||
$currentAsset,
|
$currentAsset,
|
||||||
$store.selectedComponentId,
|
$store.selectedComponentId,
|
||||||
"form"
|
"form",
|
||||||
|
{ includeSelf: nested }
|
||||||
)
|
)
|
||||||
$: schemaComponents = getContextProviderComponents(
|
$: schemaComponents = getContextProviderComponents(
|
||||||
$currentAsset,
|
$currentAsset,
|
||||||
|
|
|
@ -79,6 +79,7 @@
|
||||||
bindings={allBindings}
|
bindings={allBindings}
|
||||||
name={key}
|
name={key}
|
||||||
text={label}
|
text={label}
|
||||||
|
{nested}
|
||||||
{key}
|
{key}
|
||||||
{type}
|
{type}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
@ -1,26 +1,28 @@
|
||||||
<script>
|
<script>
|
||||||
import { Select } from "@budibase/bbui"
|
import { Select } from "@budibase/bbui"
|
||||||
import { tables } from "stores/backend"
|
import { createEventDispatcher } from "svelte"
|
||||||
|
import { tables as tablesStore } from "stores/backend"
|
||||||
|
|
||||||
export let value
|
export let value
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
$: tables = $tablesStore.list.map(m => ({
|
||||||
|
label: m.name,
|
||||||
|
tableId: m._id,
|
||||||
|
type: "table",
|
||||||
|
}))
|
||||||
|
|
||||||
|
const onChange = e => {
|
||||||
|
const dataSource = tables?.find(x => x.tableId === e.detail)
|
||||||
|
dispatch("change", dataSource)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<Select
|
||||||
<Select extraThin secondary wide on:change {value}>
|
on:change={onChange}
|
||||||
<option value="">Choose a table</option>
|
value={value?.tableId}
|
||||||
{#each $tables.list as table}
|
options={tables}
|
||||||
<option value={table._id}>{table.name}</option>
|
getOptionValue={x => x.tableId}
|
||||||
{/each}
|
getOptionLabel={x => x.label}
|
||||||
</Select>
|
/>
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
div {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
div :global(> *) {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -3637,5 +3637,255 @@
|
||||||
"key": "value"
|
"key": "value"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"formblock": {
|
||||||
|
"name": "Form Block",
|
||||||
|
"icon": "Form",
|
||||||
|
"styles": ["size"],
|
||||||
|
"block": true,
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Type",
|
||||||
|
"key": "actionType",
|
||||||
|
"options": ["Create", "Update"],
|
||||||
|
"defaultValue": "Create"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dataSource",
|
||||||
|
"label": "Schema",
|
||||||
|
"key": "dataSource"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Title",
|
||||||
|
"key": "title"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"section": true,
|
||||||
|
"name": "Fields",
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "multifield",
|
||||||
|
"label": "Fields",
|
||||||
|
"key": "fields"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Field labels",
|
||||||
|
"key": "labelPosition",
|
||||||
|
"defaultValue": "left",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "Left",
|
||||||
|
"value": "left"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Right",
|
||||||
|
"value": "right"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Above",
|
||||||
|
"value": "above"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Size",
|
||||||
|
"key": "size",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "Medium",
|
||||||
|
"value": "spectrum--medium"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Large",
|
||||||
|
"value": "spectrum--large"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"defaultValue": "spectrum--medium"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Disabled",
|
||||||
|
"key": "disabled",
|
||||||
|
"defaultValue": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"section": true,
|
||||||
|
"name": "Buttons",
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Show primary button",
|
||||||
|
"key": "showPrimaryButton",
|
||||||
|
"defaultValue": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Text",
|
||||||
|
"key": "primaryButtonText",
|
||||||
|
"defaultValue": "Submit",
|
||||||
|
"dependsOn": "showPrimaryButton"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"label": "On click",
|
||||||
|
"key": "primaryButtonOnClick",
|
||||||
|
"nested": true,
|
||||||
|
"dependsOn": "showPrimaryButton"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Show secondary button",
|
||||||
|
"key": "showSecondaryButton",
|
||||||
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Text",
|
||||||
|
"key": "secondaryButtonText",
|
||||||
|
"defaultValue": "Action",
|
||||||
|
"dependsOn": "showSecondaryButton"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"label": "On click",
|
||||||
|
"key": "secondaryButtonOnClick",
|
||||||
|
"nested": true,
|
||||||
|
"dependsOn": "showSecondaryButton"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"context": [
|
||||||
|
{
|
||||||
|
"type": "form",
|
||||||
|
"suffix": "form"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"formblockplus": {
|
||||||
|
"name": "Form Block+",
|
||||||
|
"icon": "Form",
|
||||||
|
"styles": ["size"],
|
||||||
|
"block": true,
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Type",
|
||||||
|
"key": "actionType",
|
||||||
|
"options": ["Create", "Update"],
|
||||||
|
"defaultValue": "Create"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "table",
|
||||||
|
"label": "Schema",
|
||||||
|
"key": "dataSource"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Row ID to update",
|
||||||
|
"key": "rowId",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "actionType",
|
||||||
|
"value": "Update"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Title",
|
||||||
|
"key": "title",
|
||||||
|
"nested": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"section": true,
|
||||||
|
"name": "Fields",
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "multifield",
|
||||||
|
"label": "Fields",
|
||||||
|
"key": "fields"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Field labels",
|
||||||
|
"key": "labelPosition",
|
||||||
|
"defaultValue": "left",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "Left",
|
||||||
|
"value": "left"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Right",
|
||||||
|
"value": "right"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Above",
|
||||||
|
"value": "above"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Size",
|
||||||
|
"key": "size",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "Medium",
|
||||||
|
"value": "spectrum--medium"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Large",
|
||||||
|
"value": "spectrum--large"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"defaultValue": "spectrum--medium"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Disabled",
|
||||||
|
"key": "disabled",
|
||||||
|
"defaultValue": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"section": true,
|
||||||
|
"name": "Buttons",
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Show save button",
|
||||||
|
"key": "showSaveButton",
|
||||||
|
"defaultValue": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Show delete button",
|
||||||
|
"key": "showDeleteButton",
|
||||||
|
"defaultValue": false,
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "actionType",
|
||||||
|
"value": "Update"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"context": [
|
||||||
|
{
|
||||||
|
"type": "form",
|
||||||
|
"suffix": "form"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "schema",
|
||||||
|
"suffix": "repeater"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,132 @@
|
||||||
|
<script>
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
import BlockComponent from "../../BlockComponent.svelte"
|
||||||
|
import Block from "../../Block.svelte"
|
||||||
|
import { Heading, Layout } from "@budibase/bbui"
|
||||||
|
import Placeholder from "../Placeholder.svelte"
|
||||||
|
|
||||||
|
export let actionType
|
||||||
|
export let dataSource
|
||||||
|
export let size
|
||||||
|
export let disabled
|
||||||
|
export let fields
|
||||||
|
export let labelPosition
|
||||||
|
export let title
|
||||||
|
export let showPrimaryButton
|
||||||
|
export let primaryButtonOnClick
|
||||||
|
export let primaryButtonText
|
||||||
|
export let showSecondaryButton
|
||||||
|
export let secondaryButtonOnClick
|
||||||
|
export let secondaryButtonText
|
||||||
|
|
||||||
|
const { styleable, fetchDatasourceSchema } = getContext("sdk")
|
||||||
|
const component = getContext("component")
|
||||||
|
const FieldTypeToComponentMap = {
|
||||||
|
string: "stringfield",
|
||||||
|
number: "numberfield",
|
||||||
|
options: "optionsfield",
|
||||||
|
array: "multifieldselect",
|
||||||
|
boolean: "booleanfield",
|
||||||
|
longform: "longformfield",
|
||||||
|
datetime: "datetimefield",
|
||||||
|
attachment: "attachmentfield",
|
||||||
|
link: "relationshipfield",
|
||||||
|
json: "jsonfield",
|
||||||
|
}
|
||||||
|
|
||||||
|
let schema
|
||||||
|
$: fetchSchema(dataSource)
|
||||||
|
|
||||||
|
const fetchSchema = async () => {
|
||||||
|
schema = (await fetchDatasourceSchema(dataSource)) || {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getComponentForField = field => {
|
||||||
|
if (!field || !schema?.[field]) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const type = schema[field].type
|
||||||
|
return FieldTypeToComponentMap[type]
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Block>
|
||||||
|
<div use:styleable={$component.styles}>
|
||||||
|
{#if fields?.length}
|
||||||
|
<BlockComponent
|
||||||
|
type="form"
|
||||||
|
props={{ actionType, dataSource, size, disabled }}
|
||||||
|
context="form"
|
||||||
|
>
|
||||||
|
<Layout noPadding gap="M">
|
||||||
|
<div class="title" class:with-text={!!title}>
|
||||||
|
<Heading>{title || ""}</Heading>
|
||||||
|
<div class="buttons">
|
||||||
|
{#if showSecondaryButton}
|
||||||
|
<BlockComponent
|
||||||
|
type="button"
|
||||||
|
props={{
|
||||||
|
text: secondaryButtonText,
|
||||||
|
onClick: secondaryButtonOnClick,
|
||||||
|
quiet: true,
|
||||||
|
type: "secondary",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{#if showPrimaryButton}
|
||||||
|
<BlockComponent
|
||||||
|
type="button"
|
||||||
|
props={{
|
||||||
|
text: primaryButtonText,
|
||||||
|
onClick: primaryButtonOnClick,
|
||||||
|
type: "cta",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<BlockComponent type="fieldgroup" props={{ labelPosition }}>
|
||||||
|
{#each fields as field}
|
||||||
|
{#if getComponentForField(field)}
|
||||||
|
<BlockComponent
|
||||||
|
type={getComponentForField(field)}
|
||||||
|
props={{
|
||||||
|
field,
|
||||||
|
label: field,
|
||||||
|
placeholder: field,
|
||||||
|
disabled,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</BlockComponent>
|
||||||
|
</Layout>
|
||||||
|
</BlockComponent>
|
||||||
|
{:else}
|
||||||
|
<Placeholder
|
||||||
|
text="Choose your schema and add some fields to your form to get started"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</Block>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-m);
|
||||||
|
order: 2;
|
||||||
|
}
|
||||||
|
.title.with-text {
|
||||||
|
order: 0;
|
||||||
|
}
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-m);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,199 @@
|
||||||
|
<script>
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
import BlockComponent from "../../BlockComponent.svelte"
|
||||||
|
import Block from "../../Block.svelte"
|
||||||
|
import { Layout } from "@budibase/bbui"
|
||||||
|
import Placeholder from "../Placeholder.svelte"
|
||||||
|
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||||
|
|
||||||
|
export let actionType
|
||||||
|
export let dataSource
|
||||||
|
export let size
|
||||||
|
export let disabled
|
||||||
|
export let fields
|
||||||
|
export let labelPosition
|
||||||
|
export let title
|
||||||
|
export let showSaveButton
|
||||||
|
export let showDeleteButton
|
||||||
|
export let rowId
|
||||||
|
|
||||||
|
const { styleable, fetchDatasourceSchema, builderStore } = getContext("sdk")
|
||||||
|
const component = getContext("component")
|
||||||
|
const FieldTypeToComponentMap = {
|
||||||
|
string: "stringfield",
|
||||||
|
number: "numberfield",
|
||||||
|
options: "optionsfield",
|
||||||
|
array: "multifieldselect",
|
||||||
|
boolean: "booleanfield",
|
||||||
|
longform: "longformfield",
|
||||||
|
datetime: "datetimefield",
|
||||||
|
attachment: "attachmentfield",
|
||||||
|
link: "relationshipfield",
|
||||||
|
json: "jsonfield",
|
||||||
|
}
|
||||||
|
|
||||||
|
let schema
|
||||||
|
let formId
|
||||||
|
let providerId
|
||||||
|
let repeaterId
|
||||||
|
|
||||||
|
$: fetchSchema(dataSource)
|
||||||
|
$: onSave = [
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Save Row",
|
||||||
|
parameters: {
|
||||||
|
providerId: formId,
|
||||||
|
tableId: dataSource?.tableId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Close Screen Modal",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
$: onDelete = [
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Delete Row",
|
||||||
|
parameters: {
|
||||||
|
confirm: true,
|
||||||
|
tableId: dataSource?.tableId,
|
||||||
|
rowId: `{{ ${safe(repeaterId)}.${safe("_id")} }}`,
|
||||||
|
revId: `{{ ${safe(repeaterId)}.${safe("_rev")} }}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Close Screen Modal",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
$: filter = [
|
||||||
|
{
|
||||||
|
field: "_id",
|
||||||
|
operator: "equal",
|
||||||
|
type: "string",
|
||||||
|
value: rowId,
|
||||||
|
valueType: "binding",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
// If we're using an "update" form, use the real data provider. If we're
|
||||||
|
// using a create form, we just want a fake array so that our repeater
|
||||||
|
// will actually render the form, but data doesn't matter.
|
||||||
|
$: dataProvider =
|
||||||
|
actionType === "Update"
|
||||||
|
? `{{ literal ${safe(providerId)} }}`
|
||||||
|
: { rows: [{}] }
|
||||||
|
|
||||||
|
const fetchSchema = async () => {
|
||||||
|
schema = (await fetchDatasourceSchema(dataSource)) || {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getComponentForField = field => {
|
||||||
|
if (!field || !schema?.[field]) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const type = schema[field].type
|
||||||
|
return FieldTypeToComponentMap[type]
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Block>
|
||||||
|
<div use:styleable={$component.styles}>
|
||||||
|
{#if fields?.length}
|
||||||
|
<BlockComponent
|
||||||
|
type="dataprovider"
|
||||||
|
context="provider"
|
||||||
|
bind:id={providerId}
|
||||||
|
props={{
|
||||||
|
dataSource,
|
||||||
|
filter,
|
||||||
|
limit: rowId ? 1 : $builderStore.inBuilder ? 1 : 0,
|
||||||
|
paginate: false,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<BlockComponent
|
||||||
|
type="repeater"
|
||||||
|
context="repeater"
|
||||||
|
bind:id={repeaterId}
|
||||||
|
props={{
|
||||||
|
dataProvider,
|
||||||
|
noRowsMessage: "We couldn't find a row to display",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<BlockComponent
|
||||||
|
type="form"
|
||||||
|
props={{ actionType, dataSource, size, disabled }}
|
||||||
|
context="form"
|
||||||
|
bind:id={formId}
|
||||||
|
>
|
||||||
|
<Layout noPadding gap="M">
|
||||||
|
<div class="title" class:with-text={!!title}>
|
||||||
|
<BlockComponent type="heading" props={{ text: title || "" }} />
|
||||||
|
<div class="buttons">
|
||||||
|
{#if showDeleteButton}
|
||||||
|
<BlockComponent
|
||||||
|
type="button"
|
||||||
|
props={{
|
||||||
|
text: "Delete",
|
||||||
|
onClick: onDelete,
|
||||||
|
quiet: true,
|
||||||
|
type: "secondary",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{#if showSaveButton}
|
||||||
|
<BlockComponent
|
||||||
|
type="button"
|
||||||
|
props={{
|
||||||
|
text: "Save",
|
||||||
|
onClick: onSave,
|
||||||
|
type: "cta",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<BlockComponent type="fieldgroup" props={{ labelPosition }}>
|
||||||
|
{#each fields as field}
|
||||||
|
{#if getComponentForField(field)}
|
||||||
|
<BlockComponent
|
||||||
|
type={getComponentForField(field)}
|
||||||
|
props={{
|
||||||
|
field,
|
||||||
|
label: field,
|
||||||
|
placeholder: field,
|
||||||
|
disabled,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</BlockComponent>
|
||||||
|
</Layout>
|
||||||
|
</BlockComponent>
|
||||||
|
</BlockComponent>
|
||||||
|
</BlockComponent>
|
||||||
|
{:else}
|
||||||
|
<Placeholder
|
||||||
|
text="Choose your schema and add some fields to your form to get started"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</Block>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-m);
|
||||||
|
order: 2;
|
||||||
|
}
|
||||||
|
.title.with-text {
|
||||||
|
order: 0;
|
||||||
|
}
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-m);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,3 +1,5 @@
|
||||||
export { default as tableblock } from "./TableBlock.svelte"
|
export { default as tableblock } from "./TableBlock.svelte"
|
||||||
export { default as cardsblock } from "./CardsBlock.svelte"
|
export { default as cardsblock } from "./CardsBlock.svelte"
|
||||||
export { default as repeaterblock } from "./RepeaterBlock.svelte"
|
export { default as repeaterblock } from "./RepeaterBlock.svelte"
|
||||||
|
export { default as formblock } from "./FormBlock.svelte"
|
||||||
|
export { default as formblockplus } from "./FormBlockPlus.svelte"
|
||||||
|
|
|
@ -53,7 +53,6 @@
|
||||||
palette,
|
palette,
|
||||||
horizontal
|
horizontal
|
||||||
) => {
|
) => {
|
||||||
console.log("new chart")
|
|
||||||
const allCols = [labelColumn, ...(valueColumns || [null])]
|
const allCols = [labelColumn, ...(valueColumns || [null])]
|
||||||
if (
|
if (
|
||||||
!dataProvider ||
|
!dataProvider ||
|
||||||
|
|
|
@ -40,36 +40,7 @@
|
||||||
|
|
||||||
// Fetches the form schema from this form's dataSource
|
// Fetches the form schema from this form's dataSource
|
||||||
const fetchSchema = async dataSource => {
|
const fetchSchema = async dataSource => {
|
||||||
if (!dataSource) {
|
schema = (await fetchDatasourceSchema(dataSource)) || {}
|
||||||
schema = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the datasource is a query, then we instead use a schema of the query
|
|
||||||
// parameters rather than the output schema
|
|
||||||
else if (
|
|
||||||
dataSource.type === "query" &&
|
|
||||||
dataSource._id &&
|
|
||||||
actionType === "Create"
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
const query = await API.fetchQueryDefinition(dataSource._id)
|
|
||||||
let paramSchema = {}
|
|
||||||
const params = query.parameters || []
|
|
||||||
params.forEach(param => {
|
|
||||||
paramSchema[param.name] = { ...param, type: "string" }
|
|
||||||
})
|
|
||||||
schema = paramSchema
|
|
||||||
} catch (error) {
|
|
||||||
schema = {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// For all other cases, just grab the normal schema
|
|
||||||
else {
|
|
||||||
const dataSourceSchema = await fetchDatasourceSchema(dataSource)
|
|
||||||
schema = dataSourceSchema || {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!loaded) {
|
if (!loaded) {
|
||||||
loaded = true
|
loaded = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,6 @@ const createScreenStore = () => {
|
||||||
}
|
}
|
||||||
let children = []
|
let children = []
|
||||||
findChildrenByType(component, type, children)
|
findChildrenByType(component, type, children)
|
||||||
console.log(children)
|
|
||||||
return children
|
return children
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ import JSONArrayFetch from "@budibase/frontend-core/src/fetch/JSONArrayFetch.js"
|
||||||
*/
|
*/
|
||||||
export const fetchDatasourceSchema = async (
|
export const fetchDatasourceSchema = async (
|
||||||
datasource,
|
datasource,
|
||||||
options = { enrichRelationships: false }
|
options = { enrichRelationships: false, formSchema: false }
|
||||||
) => {
|
) => {
|
||||||
const handler = {
|
const handler = {
|
||||||
table: TableFetch,
|
table: TableFetch,
|
||||||
|
@ -35,7 +35,17 @@ export const fetchDatasourceSchema = async (
|
||||||
|
|
||||||
// Get the datasource definition and then schema
|
// Get the datasource definition and then schema
|
||||||
const definition = await instance.getDefinition(datasource)
|
const definition = await instance.getDefinition(datasource)
|
||||||
let schema = instance.getSchema(datasource, definition)
|
|
||||||
|
// Get the normal schema as long as we aren't wanting a form schema
|
||||||
|
let schema
|
||||||
|
if (datasource?.type !== "query" || !options?.formSchema) {
|
||||||
|
schema = instance.getSchema(datasource, definition)
|
||||||
|
} else if (definition.parameters?.length) {
|
||||||
|
schema = {}
|
||||||
|
definition.parameters.forEach(param => {
|
||||||
|
schema[param.name] = { ...param, type: "string" }
|
||||||
|
})
|
||||||
|
}
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,6 @@ export const buildAttachmentEndpoints = API => {
|
||||||
* @param data the file to upload
|
* @param data the file to upload
|
||||||
*/
|
*/
|
||||||
externalUpload: async ({ datasourceId, bucket, key, data }) => {
|
externalUpload: async ({ datasourceId, bucket, key, data }) => {
|
||||||
console.log(API)
|
|
||||||
const { signedUrl, publicUrl } = await getSignedDatasourceURL({
|
const { signedUrl, publicUrl } = await getSignedDatasourceURL({
|
||||||
datasourceId,
|
datasourceId,
|
||||||
bucket,
|
bucket,
|
||||||
|
|
Loading…
Reference in New Issue