List refinement, Form Block UX updates for action type. Bug fixes for FormBlock bindings. TableBlock UX updates and Component Setting updates

This commit is contained in:
Dean 2023-08-24 14:39:53 +01:00
parent 046ef853e3
commit 1ec2faf74d
21 changed files with 666 additions and 485 deletions

View File

@ -23,6 +23,9 @@
export let animate = true export let animate = true
export let customZindex export let customZindex
export let showPopover = true
export let clickOutsideOverride = false
$: target = portalTarget || getContext(Context.PopoverRoot) || ".spectrum" $: target = portalTarget || getContext(Context.PopoverRoot) || ".spectrum"
export const show = () => { export const show = () => {
@ -36,6 +39,9 @@
} }
const handleOutsideClick = e => { const handleOutsideClick = e => {
if (clickOutsideOverride) {
return
}
if (open) { if (open) {
// Stop propagation if the source is the anchor // Stop propagation if the source is the anchor
let node = e.target let node = e.target
@ -54,6 +60,9 @@
} }
function handleEscape(e) { function handleEscape(e) {
if (!clickOutsideOverride) {
return
}
if (open && e.key === "Escape") { if (open && e.key === "Escape") {
hide() hide()
} }
@ -79,6 +88,7 @@
on:keydown={handleEscape} on:keydown={handleEscape}
class="spectrum-Popover is-open" class="spectrum-Popover is-open"
class:customZindex class:customZindex
class:hide-popover={open && !showPopover}
role="presentation" role="presentation"
style="height: {customHeight}; --customZindex: {customZindex};" style="height: {customHeight}; --customZindex: {customZindex};"
transition:fly|local={{ y: -20, duration: animate ? 200 : 0 }} transition:fly|local={{ y: -20, duration: animate ? 200 : 0 }}
@ -89,6 +99,10 @@
{/if} {/if}
<style> <style>
.hide-popover {
display: contents;
}
.spectrum-Popover { .spectrum-Popover {
min-width: var(--spectrum-global-dimension-size-2000); min-width: var(--spectrum-global-dimension-size-2000);
border-color: var(--spectrum-global-color-gray-300); border-color: var(--spectrum-global-color-gray-300);

View File

@ -22,6 +22,10 @@ import { TableNames } from "../constants"
import { JSONUtils } from "@budibase/frontend-core" import { JSONUtils } from "@budibase/frontend-core"
import ActionDefinitions from "components/design/settings/controls/ButtonActionEditor/manifest.json" import ActionDefinitions from "components/design/settings/controls/ButtonActionEditor/manifest.json"
import { environment, licensing } from "stores/portal" import { environment, licensing } from "stores/portal"
import {
convertOldFieldFormat,
getComponentForField,
} from "components/design/settings/controls/FieldConfiguration/utils"
// Regex to match all instances of template strings // Regex to match all instances of template strings
const CAPTURE_VAR_INSIDE_TEMPLATE = /{{([^}]+)}}/g const CAPTURE_VAR_INSIDE_TEMPLATE = /{{([^}]+)}}/g
@ -328,7 +332,7 @@ const getProviderContextBindings = (asset, dataProviders) => {
if (context.type === "form") { if (context.type === "form") {
// Forms do not need table schemas // Forms do not need table schemas
// Their schemas are built from their component field names // Their schemas are built from their component field names
schema = buildFormSchema(component) schema = buildFormSchema(component, asset)
readablePrefix = "Fields" readablePrefix = "Fields"
} else if (context.type === "static") { } else if (context.type === "static") {
// Static contexts are fully defined by the components // Static contexts are fully defined by the components
@ -370,6 +374,11 @@ const getProviderContextBindings = (asset, dataProviders) => {
if (runtimeSuffix) { if (runtimeSuffix) {
providerId += `-${runtimeSuffix}` providerId += `-${runtimeSuffix}`
} }
if (!filterCategoryByContext(component, context)) {
return
}
const safeComponentId = makePropSafe(providerId) const safeComponentId = makePropSafe(providerId)
// Create bindable properties for each schema field // Create bindable properties for each schema field
@ -387,6 +396,12 @@ const getProviderContextBindings = (asset, dataProviders) => {
} }
readableBinding += `.${fieldSchema.name || key}` readableBinding += `.${fieldSchema.name || key}`
const bindingCategory = getComponentBindingCategory(
component,
context,
def
)
// Create the binding object // Create the binding object
bindings.push({ bindings.push({
type: "context", type: "context",
@ -399,8 +414,8 @@ const getProviderContextBindings = (asset, dataProviders) => {
// Table ID is used by JSON fields to know what table the field is in // Table ID is used by JSON fields to know what table the field is in
tableId: table?._id, tableId: table?._id,
component: component._component, component: component._component,
category: component._instanceName, category: bindingCategory.category,
icon: def.icon, icon: bindingCategory.icon,
display: { display: {
name: fieldSchema.name || key, name: fieldSchema.name || key,
type: fieldSchema.type, type: fieldSchema.type,
@ -413,6 +428,40 @@ const getProviderContextBindings = (asset, dataProviders) => {
return bindings return bindings
} }
// Exclude a data context based on the component settings
const filterCategoryByContext = (component, context) => {
const { _component } = component
if (_component.endsWith("formblock")) {
if (
(component.actionType == "Create" && context.type === "schema") ||
(component.actionType == "View" && context.type === "form")
) {
return false
}
}
return true
}
const getComponentBindingCategory = (component, context, def) => {
let icon = def.icon
let category = component._instanceName
if (component._component.endsWith("formblock")) {
let contextCategorySuffix = {
form: "Fields",
schema: "Row",
}
category = `${component._instanceName} - ${
contextCategorySuffix[context.type]
}`
icon = context.type === "form" ? "Form" : "Data"
}
return {
icon,
category,
}
}
/** /**
* Gets all bindable properties from the logged in user. * Gets all bindable properties from the logged in user.
*/ */
@ -507,6 +556,7 @@ const getSelectedRowsBindings = asset => {
)}.${makePropSafe("selectedRows")}`, )}.${makePropSafe("selectedRows")}`,
readableBinding: `${block._instanceName}.Selected rows`, readableBinding: `${block._instanceName}.Selected rows`,
category: "Selected rows", category: "Selected rows",
icon: "ViewRow",
display: { name: block._instanceName }, display: { name: block._instanceName },
})) }))
) )
@ -835,18 +885,36 @@ export const getSchemaForDatasource = (asset, datasource, options) => {
* Builds a form schema given a form component. * Builds a form schema given a form component.
* A form schema is a schema of all the fields nested anywhere within a form. * A form schema is a schema of all the fields nested anywhere within a form.
*/ */
export const buildFormSchema = component => { export const buildFormSchema = (component, asset) => {
let schema = {} let schema = {}
if (!component) { if (!component) {
return schema return schema
} }
// If this is a form block, simply use the fields setting
if (component._component.endsWith("formblock")) { if (component._component.endsWith("formblock")) {
let schema = {} let schema = {}
component.fields?.forEach(field => {
schema[field] = { type: "string" } const datasource = getDatasourceForProvider(asset, component)
}) const info = getSchemaForDatasource(component, datasource)
if (!component.fields) {
Object.values(info?.schema)
.filter(
({ autocolumn, name }) =>
!autocolumn && !["_rev", "_id"].includes(name)
)
.forEach(({ name }) => {
schema[name] = { type: info?.schema[name].type }
})
} else {
// Field conversion
const patched = convertOldFieldFormat(component.fields || [])
patched?.forEach(({ field, active }) => {
if (!active) return
schema[field] = { type: info?.schema[field].type }
})
}
return schema return schema
} }
@ -862,7 +930,7 @@ export const buildFormSchema = component => {
} }
} }
component._children?.forEach(child => { component._children?.forEach(child => {
const childSchema = buildFormSchema(child) const childSchema = buildFormSchema(child, asset)
schema = { ...schema, ...childSchema } schema = { ...schema, ...childSchema }
}) })
return schema return schema

View File

@ -93,40 +93,6 @@ const INITIAL_FRONTEND_STATE = {
tourNodes: null, tourNodes: null,
} }
export const updateComponentSetting = (name, value) => {
return component => {
if (!name || !component) {
return false
}
// Skip update if the value is the same
if (component[name] === value) {
return false
}
const settings = getComponentSettings(component._component)
const updatedSetting = settings.find(setting => setting.key === name)
if (
updatedSetting?.type === "dataSource" ||
updatedSetting?.type === "table"
) {
const { schema } = getSchemaForDatasource(null, value)
const columnNames = Object.keys(schema || {})
const multifieldKeysToSelectAll = settings
.filter(setting => {
return setting.type === "multifield" && setting.selectAllFields
})
.map(setting => setting.key)
multifieldKeysToSelectAll.forEach(key => {
component[key] = columnNames
})
}
component[name] = value
}
}
export const getFrontendStore = () => { export const getFrontendStore = () => {
const store = writable({ ...INITIAL_FRONTEND_STATE }) const store = writable({ ...INITIAL_FRONTEND_STATE })
let websocket let websocket
@ -145,18 +111,14 @@ export const getFrontendStore = () => {
} }
let clone = cloneDeep(screen) let clone = cloneDeep(screen)
const result = patchFn(clone) const result = patchFn(clone)
console.log("sequentialScreenPatch ", result)
if (result === false) { if (result === false) {
return return
} }
return return await store.actions.screens.save(clone)
//return await store.actions.screens.save(clone)
}) })
store.actions = { store.actions = {
tester: (name, value) => {
return updateComponentSetting(name, value)
},
reset: () => { reset: () => {
store.set({ ...INITIAL_FRONTEND_STATE }) store.set({ ...INITIAL_FRONTEND_STATE })
websocket?.disconnect() websocket?.disconnect()
@ -864,7 +826,6 @@ export const getFrontendStore = () => {
}, },
patch: async (patchFn, componentId, screenId) => { patch: async (patchFn, componentId, screenId) => {
// Use selected component by default // Use selected component by default
console.log("front end patch")
if (!componentId || !screenId) { if (!componentId || !screenId) {
const state = get(store) const state = get(store)
componentId = componentId || state.selectedComponentId componentId = componentId || state.selectedComponentId
@ -883,18 +844,6 @@ export const getFrontendStore = () => {
} }
await store.actions.screens.patch(patchScreen, screenId) await store.actions.screens.patch(patchScreen, screenId)
}, },
// Temporary
customPatch: async (patchFn, componentId, screenId) => {
console.log("patchUpdate :")
if (!componentId || !screenId) {
const state = get(store)
componentId = componentId || state.selectedComponentId
screenId = screenId || state.selectedScreenId
}
if (!componentId || !screenId || !patchFn) {
return
}
},
delete: async component => { delete: async component => {
if (!component) { if (!component) {
return return
@ -1261,9 +1210,41 @@ export const getFrontendStore = () => {
}, },
updateSetting: async (name, value) => { updateSetting: async (name, value) => {
await store.actions.components.patch( await store.actions.components.patch(
updateComponentSetting(name, value) store.actions.components.updateComponentSetting(name, value)
) )
}, },
updateComponentSetting: (name, value) => {
return component => {
if (!name || !component) {
return false
}
// Skip update if the value is the same
if (component[name] === value) {
return false
}
const settings = getComponentSettings(component._component)
const updatedSetting = settings.find(setting => setting.key === name)
if (
updatedSetting?.type === "dataSource" ||
updatedSetting?.type === "table"
) {
const { schema } = getSchemaForDatasource(null, value)
const columnNames = Object.keys(schema || {})
const multifieldKeysToSelectAll = settings
.filter(setting => {
return setting.type === "multifield" && setting.selectAllFields
})
.map(setting => setting.key)
multifieldKeysToSelectAll.forEach(key => {
component[key] = columnNames
})
}
component[name] = value
}
},
requestEjectBlock: componentId => { requestEjectBlock: componentId => {
store.actions.preview.sendEvent("eject-block", componentId) store.actions.preview.sendEvent("eject-block", componentId)
}, },

View File

@ -74,6 +74,8 @@
{/if} {/if}
</div> </div>
<Drawer <Drawer
on:drawerHide
on:drawerShow
{fillWidth} {fillWidth}
bind:this={bindingDrawer} bind:this={bindingDrawer}
{title} {title}

View File

@ -74,7 +74,7 @@
<ActionButton on:click={openDrawer}>{actionText}</ActionButton> <ActionButton on:click={openDrawer}>{actionText}</ActionButton>
</div> </div>
<Drawer bind:this={drawer} title={"Actions"}> <Drawer bind:this={drawer} title={"Actions"} on:drawerHide on:drawerShow>
<svelte:fragment slot="description"> <svelte:fragment slot="description">
Define what actions to run. Define what actions to run.
</svelte:fragment> </svelte:fragment>

View File

@ -1,82 +1,88 @@
<script> <script>
import { Toggle, Icon } from "@budibase/bbui" import { Icon } from "@budibase/bbui"
import { dndzone } from "svelte-dnd-action" import { dndzone } from "svelte-dnd-action"
import { flip } from "svelte/animate"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
import { generate } from "shortid"
export let value = [] export let items = []
export let showHandle = true export let showHandle = true
export let rightButton export let highlighted
export let rightProps = {} export let listType
export let listTypeProps = {}
export let listItemKey
export let draggable = true
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const flipDurationMs = 150 const flipDurationMs = 150
let dragDisabled = false
let listOptions = [...value]
let anchors = {}
const updateColumnOrder = e => { let anchors = {}
listOptions = e.detail.items let draggableItems = []
const buildDragable = items => {
return items.map(item => {
return {
id: listItemKey ? item[listItemKey] : generate(),
item,
}
})
}
$: if (items) {
draggableItems = buildDragable(items)
}
const updateRowOrder = e => {
draggableItems = e.detail.items
}
const serialiseUpdate = () => {
return draggableItems.reduce((acc, ele) => {
acc.push(ele.item)
return acc
}, [])
} }
const handleFinalize = e => { const handleFinalize = e => {
updateColumnOrder(e) updateRowOrder(e)
dispatch("change", listOptions) dispatch("change", serialiseUpdate())
dragDisabled = false
} }
// This is optional and should be moved. const onItemChanged = e => {
const onToggle = item => { dispatch("itemChange", e.detail)
return e => {
console.log(`${item.name} toggled: ${e.detail}`)
item.active = e.detail
dispatch("change", listOptions)
}
} }
</script> </script>
<ul <ul
class="list-wrap" class="list-wrap"
use:dndzone={{ use:dndzone={{
items: listOptions, items: draggableItems,
flipDurationMs, flipDurationMs,
dropTargetStyle: { outline: "none" }, dropTargetStyle: { outline: "none" },
dragDisabled, dragDisabled: !draggable,
}} }}
on:finalize={handleFinalize} on:finalize={handleFinalize}
on:consider={updateColumnOrder} on:consider={updateRowOrder}
> >
{#each listOptions as item (item.id)} {#each draggableItems as draggable (draggable.id)}
<li <li
animate:flip={{ duration: flipDurationMs }} bind:this={anchors[draggable.id]}
bind:this={anchors[item.id]} class:highlighted={draggable.id === highlighted}
> >
<div class="left-content"> <div class="left-content">
{#if showHandle} {#if showHandle}
<div <div class="handle" aria-label="drag-handle">
class="handle"
aria-label="drag-handle"
style={dragDisabled ? "cursor: grab" : "cursor: grabbing"}
>
<Icon name="DragHandle" size="XL" /> <Icon name="DragHandle" size="XL" />
</div> </div>
{/if} {/if}
<!-- slot - left action -->
<Toggle on:change={onToggle(item)} text="" value={item.active} thin />
{item.name}
</div> </div>
<!-- slot - right action -->
<div class="right-content"> <div class="right-content">
{#if rightButton} <svelte:component
<svelte:component this={listType}
this={rightButton} anchor={anchors[draggable.item._id]}
anchor={anchors[item.id]} item={draggable.item}
field={item} {...listTypeProps}
componentBindings={rightProps.componentBindings} on:change={onItemChanged}
bindings={rightProps.bindings} />
parent={rightProps.parent}
/>
{/if}
</div> </div>
</li> </li>
{/each} {/each}
@ -104,7 +110,6 @@
transition: background-color ease-in-out 130ms; transition: background-color ease-in-out 130ms;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between;
border-bottom: 1px solid border-bottom: 1px solid
var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid)); var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid));
} }
@ -122,12 +127,15 @@
border-top-left-radius: var(--spectrum-table-regular-border-radius); border-top-left-radius: var(--spectrum-table-regular-border-radius);
border-top-right-radius: var(--spectrum-table-regular-border-radius); border-top-right-radius: var(--spectrum-table-regular-border-radius);
} }
.left-content { .right-content {
display: flex; flex: 1;
align-items: center; min-width: 0;
} }
.list-wrap li { .list-wrap li {
padding-left: var(--spacing-s); padding-left: var(--spacing-s);
padding-right: var(--spacing-s); padding-right: var(--spacing-s);
} }
li.highlighted {
background-color: pink;
}
</style> </style>

View File

@ -1,86 +1,58 @@
<!-- FormBlockFieldSettingsPopover -->
<script> <script>
import { Icon, Popover, Layout } from "@budibase/bbui" import { Icon, Popover, Layout } from "@budibase/bbui"
import { store } from "builderStore" import { store } from "builderStore"
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import { createEventDispatcher } from "svelte"
import ComponentSettingsSection from "../../../../../pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/ComponentSettingsSection.svelte" import ComponentSettingsSection from "../../../../../pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/ComponentSettingsSection.svelte"
export let anchor export let anchor
export let field export let field
export let componentBindings export let componentBindings
export let bindings export let bindings
export let parent
const dispatch = createEventDispatcher()
let popover let popover
let drawers = []
let sudoComponentInstance
$: sudoComponentInstance = buildSudoInstance(field) $: if (field) {
$: componentDef = store.actions.components.getDefinition(field._component) sudoComponentInstance = field
}
$: componentDef = store.actions.components.getDefinition(
sudoComponentInstance._component
)
$: parsedComponentDef = processComponentDefinitionSettings(componentDef) $: parsedComponentDef = processComponentDefinitionSettings(componentDef)
const buildSudoInstance = instance => {
let clone = cloneDeep(instance)
// only do this IF necessary
const instanceCheck = store.actions.components.createInstance(
clone._component,
{
_instanceName: instance.displayName,
field: instance.name, //Must be fixed
label: instance.displayName,
placeholder: instance.displayName,
},
{} //?
)
// mutating on load would achieve this.
// Would need to replace the entire config at this point
// console.log(instanceCheck)
return instanceCheck
}
// Ensures parent bindings are pushed down
// Confirm this
const processComponentDefinitionSettings = componentDef => { const processComponentDefinitionSettings = componentDef => {
if (!componentDef) {
return {}
}
const clone = cloneDeep(componentDef) const clone = cloneDeep(componentDef)
clone.settings.forEach(setting => { const updatedSettings = clone.settings
if (setting.type === "text") { .filter(setting => setting.key !== "field")
setting.nested = true .map(setting => {
} return { ...setting, nested: true }
}) })
clone.settings = updatedSettings
return clone return clone
} }
// Current core update setting fn
const updateSetting = async (setting, value) => { const updateSetting = async (setting, value) => {
console.log("Custom Save Setting", setting, value) const nestedComponentInstance = cloneDeep(sudoComponentInstance)
console.log("The parent", parent)
//updateBlockFieldSetting in frontend? const patchFn = store.actions.components.updateComponentSetting(
const nestedFieldInstance = cloneDeep(sudoComponentInstance) setting.key,
value
// Parse the current fields on load and check for unbuilt instances
// This is icky
let parentFieldsSettings = parent.fields.find(
pSetting => pSetting.name === nestedFieldInstance.field
) )
patchFn(nestedComponentInstance)
//In this scenario it may be best to extract const update = {
store.actions.tester(setting.key, value)(nestedFieldInstance) //mods the internal val ...nestedComponentInstance,
active: sudoComponentInstance.active,
}
parentFieldsSettings = cloneDeep(nestedFieldInstance) dispatch("change", update)
console.log("UPDATED nestedFieldInstance", nestedFieldInstance)
//Overwrite all the fields
await store.actions.components.updateSetting("fields", parent.fields)
/*
ignore/disabled _instanceName > this will be handled in the new header field.
ignore/disabled field > this should be populated and hidden.
*/
} }
</script> </script>
@ -93,69 +65,60 @@
}} }}
/> />
<Popover bind:this={popover} {anchor} align="left-outside"> <Popover
<Layout noPadding> bind:this={popover}
<!-- on:open={() => {
property-group-container - has a border, is there a scenario where it doesnt render? drawers = []
}}
FormBlock Default behaviour. {anchor}
align="left-outside"
validation: field.validation, showPopover={drawers.length == 0}
field: field.name, clickOutsideOverride={drawers.length > 0}
label: field.displayName, >
placeholder: field.displayName, <span class="popover-wrap">
<Layout noPadding noGap>
Block differences <div class="type-icon">
_instanceName: <Icon name={parsedComponentDef.icon} />
Filtered as it has been moved to own area. <span>{parsedComponentDef.name}</span>
field: </div>
Fixed - not visible. <ComponentSettingsSection
componentInstance={sudoComponentInstance}
componentBindings componentDefinition={parsedComponentDef}
These appear to be removed/invalid isScreen={false}
onUpdateSetting={updateSetting}
Bindings showSectionTitle={false}
{bindings} - working showInstanceName={false}
{bindings}
{componentBindings} {componentBindings}
componentdefinition.settings[x].nested needs to be true on:drawerShow={e => {
Are these appropriate for the form block drawers = [...drawers, e.detail]
}}
FormBlock will have to pull the settings from fields:[] on:drawerHide={e => {
drawers = drawers.slice(0, -1)
Frontend Store > updateSetting: async (name, value) }}
Performs a patch for the component settings change />
</Layout>
PropertyControl </span>
Would this behaviour require a flag?
highlighted={$store.highlightedSettingKey === setting.key}
propertyFocus={$store.propertyFocus === setting.key}
Mode filtering of fields
Create
Update
View > do we filter fields here or disable them?
Default value?? Makes no sense
Drawer actions
CRUD - how to persist to the correct location?
Its just not a thing now
- Validation
- Bindings
- Custom options.
** Options source - should this be shaped too?
Schema,
Datasource
Custom
-->
<ComponentSettingsSection
componentInstance={sudoComponentInstance}
componentDefinition={parsedComponentDef}
isScreen={false}
{bindings}
{componentBindings}
onUpdateSetting={updateSetting}
/>
</Layout>
</Popover> </Popover>
<style>
.popover-wrap {
background-color: var(--spectrum-alias-background-color-secondary);
}
.type-icon {
display: flex;
gap: var(--spacing-m);
margin: var(--spacing-xl);
margin-bottom: 0px;
height: var(--spectrum-alias-item-height-m);
padding: 0px var(--spectrum-alias-item-padding-m);
border-width: var(--spectrum-actionbutton-border-size);
border-radius: var(--spectrum-alias-border-radius-regular);
border: 1px solid
var(
--spectrum-actionbutton-m-border-color,
var(--spectrum-alias-border-color)
);
align-items: center;
}
</style>

View File

@ -1,127 +1,76 @@
<script> <script>
import { cloneDeep } from "lodash/fp" import { cloneDeep, isEqual } from "lodash/fp"
import { generate } from "shortid"
import { import {
getDatasourceForProvider, getDatasourceForProvider,
getSchemaForDatasource, getSchemaForDatasource,
} from "builderStore/dataBinding"
import { currentAsset } from "builderStore"
import SettingsList from "../SettingsList.svelte"
import { createEventDispatcher } from "svelte"
import { store, selectedScreen } from "builderStore"
import {
getBindableProperties, getBindableProperties,
getComponentBindableProperties, getComponentBindableProperties,
} from "builderStore/dataBinding" } from "builderStore/dataBinding"
import { currentAsset } from "builderStore"
import EditFieldPopover from "./EditFieldPopover.svelte" import DraggableList from "../DraggableList.svelte"
import { createEventDispatcher } from "svelte"
import { store, selectedScreen } from "builderStore"
import FieldSetting from "./FieldSetting.svelte"
import { convertOldFieldFormat, getComponentForField } from "./utils"
export let componentInstance export let componentInstance
export let value export let value
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
let sanitisedFields
let fieldList
let schema
// let assetIdCache
let cachedValue
// $: value, console.log("VALUE UPDATED")
// $: $currentAsset, console.log("currentAsset updated ", $currentAsset)
// Dean - From the inner form block - make a util $: bindings = getBindableProperties($selectedScreen, componentInstance._id)
const FieldTypeToComponentMap = { $: actionType = componentInstance.actionType
string: "stringfield", let componentBindings = []
number: "numberfield",
bigint: "bigintfield", $: if (actionType) {
options: "optionsfield", componentBindings = getComponentBindableProperties(
array: "multifieldselect", $selectedScreen,
boolean: "booleanfield", componentInstance._id
longform: "longformfield", )
datetime: "datetimefield",
attachment: "attachmentfield",
link: "relationshipfield",
json: "jsonfield",
barcodeqr: "codescanner",
} }
// Dean - From the inner form block - make a util
const getComponentForField = field => {
if (!field || !schema?.[field]) {
return null
}
const type = schema[field].type
return FieldTypeToComponentMap[type]
}
let fieldConfigList
$: datasource = getDatasourceForProvider($currentAsset, componentInstance) $: datasource = getDatasourceForProvider($currentAsset, componentInstance)
$: schema = getSchema($currentAsset, datasource)
$: if (!isEqual(value, cachedValue)) {
cachedValue = value
schema = getSchema($currentAsset, datasource)
}
$: options = Object.keys(schema || {}) $: options = Object.keys(schema || {})
$: sanitisedValue = getValidColumns(convertOldFieldFormat(value), options) $: sanitisedValue = getValidColumns(convertOldFieldFormat(value), options)
$: updateBoundValue(sanitisedValue) $: updateSanitsedFields(sanitisedValue)
$: fieldConfigList.forEach(column => { $: unconfigured = buildUnconfiguredOptions(schema, sanitisedFields)
if (!column.id) {
column.id = generate()
}
})
$: bindings = getBindableProperties(
$selectedScreen,
$store.selectedComponentId
)
$: console.log("bindings ", bindings)
$: componentBindings = getComponentBindableProperties(
$selectedScreen,
$store.selectedComponentId
)
$: console.log("componentBindings ", componentBindings)
// Builds unused ones only // Builds unused ones only
const buildListOptions = (schema, selected) => { const buildUnconfiguredOptions = (schema, selected) => {
if (!schema) {
return []
}
let schemaClone = cloneDeep(schema) let schemaClone = cloneDeep(schema)
selected.forEach(val => { selected.forEach(val => {
delete schemaClone[val.name] delete schemaClone[val.field]
}) })
return Object.keys(schemaClone) return Object.keys(schemaClone)
.filter(key => !schemaClone[key].autocolumn) .filter(key => !schemaClone[key].autocolumn)
.map(key => { .map(key => {
const col = schemaClone[key] const col = schemaClone[key]
let toggleOn = !value
return { return {
name: key, field: key,
displayName: key, active: typeof col.active != "boolean" ? toggleOn : col.active,
id: generate(),
active: typeof col.active != "boolean" ? !value : col.active,
} }
}) })
} }
/*
SUPPORT
- ["FIELD1", "FIELD2"...]
"fields": [ "First Name", "Last Name" ]
- [{name: "FIELD1", displayName: "FIELD1"}, ... only the currentlyadded fields]
* [{name: "FIELD1", displayName: "FIELD1", active: true|false}, all currently available fields]
*/
$: unconfigured = buildListOptions(schema, fieldConfigList)
const convertOldFieldFormat = fields => {
let formFields
if (typeof fields?.[0] === "string") {
formFields = fields.map(field => ({
name: field,
displayName: field,
active: true,
}))
} else {
formFields = fields
}
return (formFields || []).map(field => {
return {
...field,
active: typeof field?.active != "boolean" ? true : field?.active,
}
})
}
const getSchema = (asset, datasource) => { const getSchema = (asset, datasource) => {
const schema = getSchemaForDatasource(asset, datasource).schema const schema = getSchemaForDatasource(asset, datasource).schema
@ -134,36 +83,61 @@
return schema return schema
} }
const updateBoundValue = value => { const updateSanitsedFields = value => {
fieldConfigList = cloneDeep(value) sanitisedFields = cloneDeep(value)
} }
const getValidColumns = (columns, options) => { const getValidColumns = (columns, options) => {
if (!Array.isArray(columns) || !columns.length) { if (!Array.isArray(columns) || !columns.length) {
return [] return []
} }
// We need to account for legacy configs which would just be an array
// of strings
if (typeof columns[0] === "string") {
columns = columns.map(col => ({
name: col,
displayName: col,
}))
}
return columns.filter(column => { return columns.filter(column => {
return options.includes(column.name) return options.includes(column.field)
}) })
} }
let listOptions const buildSudoInstance = instance => {
$: if (fieldConfigList) { if (instance._component) {
listOptions = [...fieldConfigList, ...unconfigured].map(column => { return instance
const type = getComponentForField(column.name) }
const _component = `@budibase/standard-components/${type}`
return { ...column, _component } //only necessary if it doesnt exist const type = getComponentForField(instance.field, schema)
instance._component = `@budibase/standard-components/${type}`
const sudoComponentInstance = store.actions.components.createInstance(
instance._component,
{
_instanceName: instance.field,
field: instance.field,
label: instance.field,
placeholder: instance.field,
},
{}
)
return { ...instance, ...sudoComponentInstance }
}
$: if (sanitisedFields) {
fieldList = [...sanitisedFields, ...unconfigured].map(buildSudoInstance)
}
const processItemUpdate = e => {
const updatedField = e.detail
const parentFieldsUpdated = fieldList ? cloneDeep(fieldList) : []
let parentFieldIdx = parentFieldsUpdated.findIndex(pSetting => {
return pSetting.field === updatedField?.field
}) })
console.log(listOptions)
if (parentFieldIdx == -1) {
parentFieldsUpdated.push(updatedField)
} else {
parentFieldsUpdated[parentFieldIdx] = updatedField
}
// fieldList = parentFieldsUpdated
dispatch("change", getValidColumns(parentFieldsUpdated, options))
} }
const listUpdated = e => { const listUpdated = e => {
@ -173,12 +147,19 @@
</script> </script>
<div class="field-configuration"> <div class="field-configuration">
<SettingsList {#if fieldList?.length}
value={listOptions} <DraggableList
on:change={listUpdated} on:change={listUpdated}
rightButton={EditFieldPopover} on:itemChange={processItemUpdate}
rightProps={{ componentBindings, bindings, parent: componentInstance }} items={fieldList}
/> listItemKey={"_id"}
listType={FieldSetting}
listTypeProps={{
componentBindings,
bindings,
}}
/>
{/if}
</div> </div>
<style> <style>

View File

@ -0,0 +1,56 @@
<script>
import EditFieldPopover from "./EditFieldPopover.svelte"
import { Toggle } from "@budibase/bbui"
import { createEventDispatcher } from "svelte"
import { cloneDeep } from "lodash/fp"
export let item
export let componentBindings
export let bindings
export let anchor
const dispatch = createEventDispatcher()
const onToggle = item => {
return e => {
item.active = e.detail
dispatch("change", { ...cloneDeep(item), active: e.detail })
}
}
</script>
<div class="list-item-body">
<div class="list-item-left">
<EditFieldPopover
{anchor}
field={item}
{componentBindings}
{bindings}
on:change
/>
<div class="field-label">{item.label || item.field}</div>
</div>
<div class="list-item-right">
<Toggle on:change={onToggle(item)} text="" value={item.active} thin />
</div>
</div>
<style>
.field-label {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.list-item-body,
.list-item-left {
display: flex;
align-items: center;
gap: var(--spacing-m);
min-width: 0;
}
.list-item-right :global(div.spectrum-Switch) {
margin: 0px;
}
.list-item-body {
justify-content: space-between;
}
</style>

View File

@ -0,0 +1,46 @@
export const convertOldFieldFormat = fields => {
if (!fields) {
return []
}
const converted = fields.map(field => {
if (typeof field === "string") {
// existed but was a string
return {
field,
active: true,
}
} else if (typeof field?.active != "boolean") {
// existed but had no state
return {
field: field.name,
active: true,
}
} else {
return field
}
})
return converted
}
export const getComponentForField = (field, schema) => {
if (!field || !schema?.[field]) {
return null
}
const type = schema[field].type
return FieldTypeToComponentMap[type]
}
export const FieldTypeToComponentMap = {
string: "stringfield",
number: "numberfield",
bigint: "bigintfield",
options: "optionsfield",
array: "multifieldselect",
boolean: "booleanfield",
longform: "longformfield",
datetime: "datetimefield",
attachment: "attachmentfield",
link: "relationshipfield",
json: "jsonfield",
barcodeqr: "codescanner",
}

View File

@ -24,11 +24,22 @@
} }
</script> </script>
<ActionButton on:click={drawer.show}>Define Options</ActionButton> <div class="options-wrap">
<Drawer bind:this={drawer} title="Options"> <div />
<div><ActionButton on:click={drawer.show}>Define Options</ActionButton></div>
</div>
<Drawer bind:this={drawer} title="Options" on:drawerHide on:drawerShow>
<svelte:fragment slot="description"> <svelte:fragment slot="description">
Define the options for this picker. Define the options for this picker.
</svelte:fragment> </svelte:fragment>
<Button cta slot="buttons" on:click={saveOptions}>Save</Button> <Button cta slot="buttons" on:click={saveOptions}>Save</Button>
<OptionsDrawer bind:options={tempValue} slot="body" /> <OptionsDrawer bind:options={tempValue} slot="body" />
</Drawer> </Drawer>
<style>
.options-wrap {
gap: 8px;
display: grid;
grid-template-columns: 90px 1fr;
}
</style>

View File

@ -100,6 +100,8 @@
{key} {key}
{type} {type}
{...props} {...props}
on:drawerHide
on:drawerShow
/> />
</div> </div>
{#if info} {#if info}

View File

@ -5,9 +5,8 @@
export let value = [] export let value = []
export let bindings = [] export let bindings = []
export let componentDefinition export let componentInstance
export let type export let type
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
let drawer let drawer
@ -31,7 +30,7 @@
<ActionButton on:click={drawer.show}>{text}</ActionButton> <ActionButton on:click={drawer.show}>{text}</ActionButton>
</div> </div>
<Drawer bind:this={drawer} title="Validation Rules"> <Drawer bind:this={drawer} title="Validation Rules" on:drawerHide on:drawerShow>
<svelte:fragment slot="description"> <svelte:fragment slot="description">
Configure validation rules for this field. Configure validation rules for this field.
</svelte:fragment> </svelte:fragment>
@ -41,7 +40,7 @@
bind:rules={value} bind:rules={value}
{type} {type}
{bindings} {bindings}
{componentDefinition} fieldName={componentInstance?.field}
/> />
</Drawer> </Drawer>

View File

@ -1,36 +1,12 @@
<script> <script>
import { DetailSummary, Icon } from "@budibase/bbui" import { DetailSummary } from "@budibase/bbui"
import InfoDisplay from "./InfoDisplay.svelte"
export let componentDefinition export let componentDefinition
</script> </script>
<DetailSummary collapsible={false}> <DetailSummary collapsible={false} noPadding={true}>
<div class="info"> <InfoDisplay
<div class="title"> title={componentDefinition.name}
<Icon name="HelpOutline" /> body={componentDefinition.info}
{componentDefinition.name} />
</div>
{componentDefinition.info}
</div>
</DetailSummary> </DetailSummary>
<style>
.title {
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
margin-bottom: var(--spacing-m);
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: var(--spacing-s);
color: var(--spectrum-global-color-gray-600);
}
.info {
padding: var(--spacing-m) var(--spacing-l) var(--spacing-l) var(--spacing-l);
background-color: var(--background-alt);
border-radius: var(--border-radius-s);
font-size: 13px;
}
</style>

View File

@ -5,7 +5,7 @@
import DesignSection from "./DesignSection.svelte" import DesignSection from "./DesignSection.svelte"
import CustomStylesSection from "./CustomStylesSection.svelte" import CustomStylesSection from "./CustomStylesSection.svelte"
import ConditionalUISection from "./ConditionalUISection.svelte" import ConditionalUISection from "./ConditionalUISection.svelte"
import ComponentInfoSection from "./ComponentInfoSection.svelte"
import { import {
getBindableProperties, getBindableProperties,
getComponentBindableProperties, getComponentBindableProperties,
@ -55,9 +55,6 @@
</div> </div>
</span> </span>
{#if section == "settings"} {#if section == "settings"}
{#if componentDefinition?.info}
<ComponentInfoSection {componentDefinition} />
{/if}
<ComponentSettingsSection <ComponentSettingsSection
{componentInstance} {componentInstance}
{componentDefinition} {componentDefinition}

View File

@ -6,6 +6,7 @@
import ResetFieldsButton from "components/design/settings/controls/ResetFieldsButton.svelte" import ResetFieldsButton from "components/design/settings/controls/ResetFieldsButton.svelte"
import EjectBlockButton from "components/design/settings/controls/EjectBlockButton.svelte" import EjectBlockButton from "components/design/settings/controls/EjectBlockButton.svelte"
import { getComponentForSetting } from "components/design/settings/componentSettings" import { getComponentForSetting } from "components/design/settings/componentSettings"
import InfoDisplay from "./InfoDisplay.svelte"
import analytics, { Events } from "analytics" import analytics, { Events } from "analytics"
export let componentDefinition export let componentDefinition
@ -14,15 +15,13 @@
export let componentBindings export let componentBindings
export let isScreen = false export let isScreen = false
export let onUpdateSetting export let onUpdateSetting
export let showSectionTitle = true
export let showInstanceName = true
$: sections = getSections(componentInstance, componentDefinition, isScreen) $: sections = getSections(componentInstance, componentDefinition, isScreen)
const getSections = (instance, definition, isScreen) => { const getSections = (instance, definition, isScreen) => {
const settings = definition?.settings ?? [] const settings = definition?.settings ?? []
console.log(
"ComponentSettingsSection::definition?.settings",
definition?.settings
)
const generalSettings = settings.filter(setting => !setting.section) const generalSettings = settings.filter(setting => !setting.section)
const customSections = settings.filter(setting => setting.section) const customSections = settings.filter(setting => setting.section)
let sections = [ let sections = [
@ -51,23 +50,22 @@
} }
const updateSetting = async (setting, value) => { const updateSetting = async (setting, value) => {
if (typeof onUpdateSetting === "function") { try {
onUpdateSetting(setting, value) if (typeof onUpdateSetting === "function") {
} else { await onUpdateSetting(setting, value)
try { } else {
await store.actions.components.updateSetting(setting.key, value) await store.actions.components.updateSetting(setting.key, value)
// Send event if required
if (setting.sendEvents) {
analytics.captureEvent(Events.COMPONENT_UPDATED, {
name: componentInstance._component,
setting: setting.key,
value,
})
}
} catch (error) {
notifications.error("Error updating component prop")
} }
// Send event if required
if (setting.sendEvents) {
analytics.captureEvent(Events.COMPONENT_UPDATED, {
name: componentInstance._component,
setting: setting.key,
value,
})
}
} catch (error) {
notifications.error("Error updating component prop")
} }
} }
@ -106,7 +104,7 @@
} }
} }
return true return typeof setting.visible == "boolean" ? setting.visible : true
} }
const canRenderControl = (instance, setting, isScreen) => { const canRenderControl = (instance, setting, isScreen) => {
@ -125,9 +123,22 @@
{#each sections as section, idx (section.name)} {#each sections as section, idx (section.name)}
{#if section.visible} {#if section.visible}
<DetailSummary name={section.name} collapsible={false}> <DetailSummary
name={showSectionTitle ? section.name : ""}
collapsible={false}
>
{#if section.info}
<div class="section-info">
<InfoDisplay body={section.info} />
</div>
{:else if idx === 0 && section.name === "General" && componentDefinition.info}
<InfoDisplay
title={componentDefinition.name}
body={componentDefinition.info}
/>
{/if}
<div class="settings"> <div class="settings">
{#if idx === 0 && !componentInstance._component.endsWith("/layout") && !isScreen} {#if idx === 0 && !componentInstance._component.endsWith("/layout") && !isScreen && showInstanceName}
<PropertyControl <PropertyControl
control={Input} control={Input}
label="Name" label="Name"
@ -138,13 +149,10 @@
{/if} {/if}
{#each section.settings as setting (setting.key)} {#each section.settings as setting (setting.key)}
{#if setting.visible} {#if setting.visible}
<!-- DEAN - Remove fieldConfiguration label config -->
<PropertyControl <PropertyControl
type={setting.type} type={setting.type}
control={getComponentForSetting(setting)} control={getComponentForSetting(setting)}
label={setting.type != "fieldConfiguration" label={setting.label}
? setting.label
: undefined}
labelHidden={setting.labelHidden} labelHidden={setting.labelHidden}
key={setting.key} key={setting.key}
value={componentInstance[setting.key]} value={componentInstance[setting.key]}
@ -169,6 +177,8 @@
{componentBindings} {componentBindings}
{componentInstance} {componentInstance}
{componentDefinition} {componentDefinition}
on:drawerShow
on:drawerHide
/> />
{/if} {/if}
{/each} {/each}

View File

@ -0,0 +1,61 @@
<script>
import { Icon } from "@budibase/bbui"
export let title
export let body
export let icon = "HelpOutline"
</script>
<div class="info" class:noTitle={!title}>
{#if title}
<div class="title">
<Icon name={icon} />
{title || ""}
</div>
{@html body}
{:else}
<span class="icon">
<Icon name={icon} />
</span>
{@html body}
{/if}
</div>
<style>
.title {
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
margin-bottom: var(--spacing-m);
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: var(--spacing-s);
}
.title,
.icon {
color: var(--spectrum-global-color-gray-600);
}
.info {
padding: var(--spacing-m) var(--spacing-l) var(--spacing-l) var(--spacing-l);
background-color: var(--background-alt);
border-radius: var(--border-radius-s);
font-size: 13px;
}
.noTitle {
display: flex;
align-items: center;
gap: var(--spacing-m);
}
.info :global(a) {
color: inherit;
transition: color 130ms ease-out;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.info :global(a:hover) {
color: var(--spectrum-global-color-gray-900);
}
</style>

View File

@ -5282,6 +5282,11 @@
}, },
{ {
"section": true, "section": true,
"dependsOn": {
"setting": "actionType",
"value": "Create",
"invert": true
},
"name": "Row details", "name": "Row details",
"info": "<a href='https://docs.budibase.com/docs/form-block' target='_blank'>How to pass a row ID using bindings</a>", "info": "<a href='https://docs.budibase.com/docs/form-block' target='_blank'>How to pass a row ID using bindings</a>",
"settings": [ "settings": [
@ -5289,23 +5294,13 @@
"type": "text", "type": "text",
"label": "Row ID", "label": "Row ID",
"key": "rowId", "key": "rowId",
"nested": true, "nested": true
"dependsOn": {
"setting": "actionType",
"value": "Create",
"invert": true
}
}, },
{ {
"type": "text", "type": "text",
"label": "Empty text", "label": "Empty text",
"key": "noRowsMessage", "key": "noRowsMessage",
"defaultValue": "We couldn't find a row to display", "defaultValue": "We couldn't find a row to display"
"dependsOn": {
"setting": "actionType",
"value": "Create",
"invert": true
}
} }
] ]
}, },
@ -5399,6 +5394,7 @@
{ {
"type": "fieldConfiguration", "type": "fieldConfiguration",
"key": "fields", "key": "fields",
"nested": true,
"selectAllFields": true "selectAllFields": true
}, },
{ {

View File

@ -45,6 +45,9 @@
let enrichedSearchColumns let enrichedSearchColumns
let schemaLoaded = false let schemaLoaded = false
// Accommodate old config to ensure delete button does not reappear
$: deleteLabel = sidePanelShowDelete === false ? "" : sidePanelDeleteLabel
$: fetchSchema(dataSource) $: fetchSchema(dataSource)
$: enrichSearchColumns(searchColumns, schema).then( $: enrichSearchColumns(searchColumns, schema).then(
val => (enrichedSearchColumns = val) val => (enrichedSearchColumns = val)
@ -245,10 +248,8 @@
bind:id={detailsFormBlockId} bind:id={detailsFormBlockId}
props={{ props={{
dataSource, dataSource,
showSaveButton: true, saveButtonLabel: sidePanelSaveLabel || "Save", //always show
showDeleteButton: sidePanelShowDelete, deleteButtonLabel: deleteLabel, //respect config
saveButtonLabel: sidePanelSaveLabel,
deleteButtonLabel: sidePanelDeleteLabel,
actionType: "Update", actionType: "Update",
rowId: `{{ ${safe("state")}.${safe(stateKey)} }}`, rowId: `{{ ${safe("state")}.${safe(stateKey)} }}`,
fields: sidePanelFields || normalFields, fields: sidePanelFields || normalFields,

View File

@ -12,55 +12,59 @@
export let fields export let fields
export let labelPosition export let labelPosition
export let title export let title
export let showDeleteButton
export let showSaveButton
export let saveButtonLabel export let saveButtonLabel
export let deleteButtonLabel export let deleteButtonLabel
export let showSaveButton
export let showDeleteButton
export let rowId export let rowId
export let actionUrl export let actionUrl
export let noRowsMessage export let noRowsMessage
export let notificationOverride export let notificationOverride
// Accommodate old config to ensure delete button does not reappear
$: deleteLabel = showDeleteButton === false ? "" : deleteButtonLabel?.trim()
$: saveLabel = showSaveButton === false ? "" : saveButtonLabel?.trim()
const { fetchDatasourceSchema } = getContext("sdk") const { fetchDatasourceSchema } = getContext("sdk")
const convertOldFieldFormat = fields => { const convertOldFieldFormat = fields => {
return typeof fields?.[0] === "string" if (!fields) {
? fields.map(field => ({ return []
}
return fields.map(field => {
if (typeof field === "string") {
// existed but was a string
return {
name: field, name: field,
displayName: field,
active: true, active: true,
})) }
: fields } else {
} // existed but had no state
//All settings need to derive from the block config now
// Parse the fields here too. Not present means false.
const getDefaultFields = (fields, schema) => {
let formFields
if (schema && (!fields || fields.length === 0)) {
const defaultFields = []
Object.values(schema).forEach(field => {
if (field.autocolumn) return
defaultFields.push({
name: field.name,
displayName: field.name,
active: true,
})
})
formFields = [...defaultFields]
} else {
formFields = (fields || []).map(field => {
return { return {
...field, ...field,
active: typeof field?.active != "boolean" ? true : field?.active, active: typeof field?.active != "boolean" ? true : field?.active,
} }
}) }
} })
}
return formFields.filter(field => field.active) const getDefaultFields = (fields, schema) => {
if (!schema) {
return []
}
let defaultFields = []
if (!fields || fields.length === 0) {
Object.values(schema)
.filter(field => !field.autocolumn)
.forEach(field => {
defaultFields.push({
name: field.name,
active: true,
})
})
}
return [...fields, ...defaultFields].filter(field => field.active)
} }
let schema let schema
@ -94,15 +98,12 @@
fields: fieldsOrDefault, fields: fieldsOrDefault,
labelPosition, labelPosition,
title, title,
saveButtonLabel, saveButtonLabel: saveLabel,
deleteButtonLabel, deleteButtonLabel: deleteLabel,
showSaveButton,
showDeleteButton,
schema, schema,
repeaterId, repeaterId,
notificationOverride, notificationOverride,
} }
const fetchSchema = async () => { const fetchSchema = async () => {
schema = (await fetchDatasourceSchema(dataSource)) || {} schema = (await fetchDatasourceSchema(dataSource)) || {}
} }

View File

@ -13,8 +13,6 @@
export let title export let title
export let saveButtonLabel export let saveButtonLabel
export let deleteButtonLabel export let deleteButtonLabel
export let showSaveButton
export let showDeleteButton
export let schema export let schema
export let repeaterId export let repeaterId
export let notificationOverride export let notificationOverride
@ -100,18 +98,33 @@
}, },
] ]
$: renderDeleteButton = showDeleteButton && actionType === "Update" $: renderDeleteButton = deleteButtonLabel && actionType === "Update"
$: renderSaveButton = showSaveButton && actionType !== "View" $: renderSaveButton = saveButtonLabel && actionType !== "View"
$: renderButtons = renderDeleteButton || renderSaveButton $: renderButtons = renderDeleteButton || renderSaveButton
$: renderHeader = renderButtons || title $: renderHeader = renderButtons || title
const getComponentForField = field => { const getComponentForField = field => {
if (!field || !schema?.[field]) { const fieldSchemaName = field.field || field.name
if (!fieldSchemaName || !schema?.[fieldSchemaName]) {
return null return null
} }
const type = schema[field].type const type = schema[fieldSchemaName].type
return FieldTypeToComponentMap[type] return FieldTypeToComponentMap[type]
} }
const getPropsForField = field => {
let fieldProps = field._component
? {
...field,
}
: {
field: field.name,
label: field.name,
placeholder: field.name,
_instanceName: field.name,
}
return fieldProps
}
</script> </script>
{#if fields?.length} {#if fields?.length}
@ -175,7 +188,7 @@
<BlockComponent <BlockComponent
type="button" type="button"
props={{ props={{
text: deleteButtonLabel || "Delete", text: deleteButtonLabel,
onClick: onDelete, onClick: onDelete,
quiet: true, quiet: true,
type: "secondary", type: "secondary",
@ -187,7 +200,7 @@
<BlockComponent <BlockComponent
type="button" type="button"
props={{ props={{
text: saveButtonLabel || "Save", text: saveButtonLabel,
onClick: onSave, onClick: onSave,
type: "cta", type: "cta",
}} }}
@ -201,15 +214,10 @@
{#key fields} {#key fields}
<BlockComponent type="fieldgroup" props={{ labelPosition }} order={1}> <BlockComponent type="fieldgroup" props={{ labelPosition }} order={1}>
{#each fields as field, idx} {#each fields as field, idx}
{#if getComponentForField(field.name)} {#if getComponentForField(field) && field.active}
<BlockComponent <BlockComponent
type={getComponentForField(field.name)} type={getComponentForField(field)}
props={{ props={getPropsForField(field)}
validation: field.validation,
field: field.name,
label: field.displayName,
placeholder: field.displayName,
}}
order={idx} order={idx}
/> />
{/if} {/if}