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:
parent
046ef853e3
commit
1ec2faf74d
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
},
|
},
|
||||||
|
|
|
@ -74,6 +74,8 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<Drawer
|
<Drawer
|
||||||
|
on:drawerHide
|
||||||
|
on:drawerShow
|
||||||
{fillWidth}
|
{fillWidth}
|
||||||
bind:this={bindingDrawer}
|
bind:this={bindingDrawer}
|
||||||
{title}
|
{title}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
@ -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",
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -100,6 +100,8 @@
|
||||||
{key}
|
{key}
|
||||||
{type}
|
{type}
|
||||||
{...props}
|
{...props}
|
||||||
|
on:drawerHide
|
||||||
|
on:drawerShow
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{#if info}
|
{#if info}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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>
|
|
@ -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
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)) || {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
Loading…
Reference in New Issue