Initial pass at the multistep form block
This commit is contained in:
parent
f0603f7edc
commit
1a03d9d729
|
@ -5,6 +5,7 @@
|
||||||
export let name
|
export let name
|
||||||
export let show = false
|
export let show = false
|
||||||
export let collapsible = true
|
export let collapsible = true
|
||||||
|
export let noPadding = false
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onHeaderClick = () => {
|
const onHeaderClick = () => {
|
||||||
|
@ -31,6 +32,7 @@
|
||||||
class="property-panel"
|
class="property-panel"
|
||||||
class:show={show || !collapsible}
|
class:show={show || !collapsible}
|
||||||
class:no-title={!name}
|
class:no-title={!name}
|
||||||
|
class:no-padding={noPadding}
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
@ -84,6 +86,10 @@
|
||||||
padding: var(--spacing-xl);
|
padding: var(--spacing-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.property-panel.no-title.no-padding {
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.show {
|
.show {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
@ -34,6 +34,9 @@
|
||||||
$: canAddButtons = max == null || buttonList.length < max
|
$: canAddButtons = max == null || buttonList.length < max
|
||||||
|
|
||||||
const sanitizeValue = val => {
|
const sanitizeValue = val => {
|
||||||
|
if (!Array.isArray(val)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
return val?.map(button => {
|
return val?.map(button => {
|
||||||
return button._component ? button : buildPseudoInstance(button)
|
return button._component ? button : buildPseudoInstance(button)
|
||||||
})
|
})
|
||||||
|
|
|
@ -39,7 +39,10 @@
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
$: datasource = getDatasourceForProvider($currentAsset, componentInstance)
|
$: datasource =
|
||||||
|
componentInstance.dataSource ||
|
||||||
|
getDatasourceForProvider($currentAsset, componentInstance)
|
||||||
|
|
||||||
$: resourceId = datasource?.resourceId || datasource?.tableId
|
$: resourceId = datasource?.resourceId || datasource?.tableId
|
||||||
|
|
||||||
$: if (!isEqual(value, cachedValue)) {
|
$: if (!isEqual(value, cachedValue)) {
|
||||||
|
|
|
@ -0,0 +1,274 @@
|
||||||
|
<script>
|
||||||
|
// import PropertyControl from "components/design/settings/controls/PropertyControl.svelte"
|
||||||
|
// import { getComponentForSetting } from "components/design/settings/componentSettings"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
import ComponentSettingsSection from "../../../../pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte"
|
||||||
|
import { getDatasourceForProvider } from "builderStore/dataBinding"
|
||||||
|
import { currentAsset, store } from "builderStore"
|
||||||
|
import { Helpers } from "@budibase/bbui"
|
||||||
|
import FormStepControls from "./FormStepControls.svelte"
|
||||||
|
import { setContext } from "svelte"
|
||||||
|
import { writable } from "svelte/store"
|
||||||
|
|
||||||
|
export let componentInstance
|
||||||
|
export let componentBindings
|
||||||
|
export let value //steps
|
||||||
|
export let bindings
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
let stepState = [...(value || [])]
|
||||||
|
|
||||||
|
const stepStore = writable({
|
||||||
|
stepsCount: stepState?.length || 1,
|
||||||
|
currentStep: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
setContext("step-form-block", stepStore)
|
||||||
|
|
||||||
|
$: ({ currentStep } = $stepStore)
|
||||||
|
$: lastIdx = stepState?.length - 1
|
||||||
|
|
||||||
|
$: if (stepState.length) {
|
||||||
|
stepStore.update(state => ({
|
||||||
|
...state,
|
||||||
|
stepsCount: stepState.length || 0,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step Definition Settings
|
||||||
|
let compSettings = [
|
||||||
|
{
|
||||||
|
customType: "formStepControl",
|
||||||
|
label: "Multi-steps",
|
||||||
|
key: "steps",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
label: "Title",
|
||||||
|
key: "title",
|
||||||
|
nested: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
label: "Description",
|
||||||
|
key: "desc",
|
||||||
|
nested: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "fieldConfiguration",
|
||||||
|
key: "fields",
|
||||||
|
nested: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "buttonConfiguration",
|
||||||
|
label: "Buttons",
|
||||||
|
key: "buttons",
|
||||||
|
wide: true,
|
||||||
|
nested: true,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const emitCurrentStep = step => {
|
||||||
|
store.actions.preview.sendEvent("builder-meta", {
|
||||||
|
component: {
|
||||||
|
_id: componentInstance._id,
|
||||||
|
step: step,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$: dataSource = getDatasourceForProvider($currentAsset, componentInstance)
|
||||||
|
|
||||||
|
$: stepDef = {
|
||||||
|
component: "@budibase/standard-components/multistepformblock-step",
|
||||||
|
name: "Formblock step",
|
||||||
|
settings: compSettings,
|
||||||
|
}
|
||||||
|
|
||||||
|
const addStep = () => {
|
||||||
|
const newStepIdx = currentStep + 1
|
||||||
|
|
||||||
|
stepState = [
|
||||||
|
...stepState.slice(0, newStepIdx),
|
||||||
|
{},
|
||||||
|
...stepState.slice(newStepIdx),
|
||||||
|
]
|
||||||
|
|
||||||
|
stepStore.update(state => ({
|
||||||
|
...state,
|
||||||
|
currentStep: newStepIdx,
|
||||||
|
}))
|
||||||
|
|
||||||
|
dispatch("change", stepState)
|
||||||
|
emitCurrentStep(newStepIdx)
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeStep = () => {
|
||||||
|
const clone = stepState.map(x => x)
|
||||||
|
clone.splice(currentStep, 1)
|
||||||
|
|
||||||
|
const targetStepIdx = Math.max(currentStep - 1, 0)
|
||||||
|
stepState = clone.map(x => x)
|
||||||
|
|
||||||
|
stepStore.update(state => ({
|
||||||
|
...state,
|
||||||
|
currentStep: targetStepIdx,
|
||||||
|
}))
|
||||||
|
|
||||||
|
dispatch("change", stepState)
|
||||||
|
emitCurrentStep(targetStepIdx)
|
||||||
|
}
|
||||||
|
|
||||||
|
const previousStep = () => {
|
||||||
|
const prevStepIdx = Math.max(currentStep - 1, 0)
|
||||||
|
stepStore.update(state => ({
|
||||||
|
...state,
|
||||||
|
currentStep: prevStepIdx,
|
||||||
|
}))
|
||||||
|
emitCurrentStep(prevStepIdx)
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextStep = () => {
|
||||||
|
const nextStepIdx = currentStep + 1
|
||||||
|
stepStore.update(state => ({
|
||||||
|
...state,
|
||||||
|
currentStep: Math.min(nextStepIdx, stepState.length - 1),
|
||||||
|
}))
|
||||||
|
emitCurrentStep(nextStepIdx)
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateStep = (field, val) => {
|
||||||
|
stepState[currentStep] ||= {}
|
||||||
|
stepState[currentStep][field.key] = val
|
||||||
|
dispatch("change", stepState)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleStepAction = action => {
|
||||||
|
switch (action) {
|
||||||
|
case "addStep":
|
||||||
|
addStep()
|
||||||
|
break
|
||||||
|
case "removeStep":
|
||||||
|
removeStep()
|
||||||
|
break
|
||||||
|
case "nextStep":
|
||||||
|
nextStep()
|
||||||
|
break
|
||||||
|
case "previousStep":
|
||||||
|
previousStep()
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
console.log("Nothing")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const processUpdate = (field, val) => {
|
||||||
|
if (field.key === "steps") {
|
||||||
|
handleStepAction(val.action)
|
||||||
|
} else {
|
||||||
|
updateStep(field, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multistep button generation
|
||||||
|
// This kind of behaviour should be shifted to frontend-core
|
||||||
|
/*
|
||||||
|
Also, look at this unicorn
|
||||||
|
\
|
||||||
|
\_
|
||||||
|
/.(((
|
||||||
|
(,/"(((__,--.
|
||||||
|
\ ) _( /{
|
||||||
|
|| " ||
|
||||||
|
|| ||
|
||||||
|
'' ''
|
||||||
|
*/
|
||||||
|
const generateButtons = () => {
|
||||||
|
const buttons = []
|
||||||
|
|
||||||
|
if (stepState.length > 1) {
|
||||||
|
if (currentStep > 0) {
|
||||||
|
buttons.push({
|
||||||
|
_id: Helpers.uuid(),
|
||||||
|
_component: "@budibase/standard-components/button",
|
||||||
|
_instanceName: Helpers.uuid(),
|
||||||
|
text: "Back",
|
||||||
|
type: "secondary",
|
||||||
|
size: "M",
|
||||||
|
onClick: [
|
||||||
|
{
|
||||||
|
parameters: {
|
||||||
|
type: "prev",
|
||||||
|
componentId: `${componentInstance._id}-form`,
|
||||||
|
},
|
||||||
|
"##eventHandlerType": "Change Form Step",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
buttons.push({
|
||||||
|
_id: Helpers.uuid(),
|
||||||
|
_component: "@budibase/standard-components/button",
|
||||||
|
_instanceName: Helpers.uuid(),
|
||||||
|
text: "Next",
|
||||||
|
type: "cta",
|
||||||
|
size: "M",
|
||||||
|
disabled: lastIdx === currentStep,
|
||||||
|
onClick: [
|
||||||
|
{
|
||||||
|
parameters: {
|
||||||
|
type: "next",
|
||||||
|
componentId: `${componentInstance._id}-form`,
|
||||||
|
},
|
||||||
|
"##eventHandlerType": "Change Form Step",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return buttons
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildPseudoInstance = ({ buttons, fields, title, desc }) => {
|
||||||
|
return {
|
||||||
|
_id: Helpers.uuid(),
|
||||||
|
_component: "@budibase/standard-components/multistepformblock-step",
|
||||||
|
buttons: buttons || generateButtons(),
|
||||||
|
fields,
|
||||||
|
title,
|
||||||
|
desc,
|
||||||
|
dataSource,
|
||||||
|
step: currentStep + 1,
|
||||||
|
_instanceName: `Step ${currentStep + 1}`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$: stepConfigInstance = buildPseudoInstance(stepState?.[currentStep] || {})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<span class="settings-wrap">
|
||||||
|
<ComponentSettingsSection
|
||||||
|
includeHidden
|
||||||
|
componentInstance={stepConfigInstance}
|
||||||
|
componentDefinition={stepDef}
|
||||||
|
onUpdateSetting={processUpdate}
|
||||||
|
getCustomComponent={type => {
|
||||||
|
const types = { formStepControl: FormStepControls }
|
||||||
|
return types[type]
|
||||||
|
}}
|
||||||
|
getCustomSectionTitle={section => {
|
||||||
|
console.log(section.name)
|
||||||
|
if (section.name === "Details" && stepState?.length > 0) {
|
||||||
|
return `Details (${currentStep}/${stepState?.length})`
|
||||||
|
}
|
||||||
|
return section.name
|
||||||
|
}}
|
||||||
|
showSectionTitle={false}
|
||||||
|
showInstanceName={false}
|
||||||
|
isScreen={false}
|
||||||
|
noPadding={true}
|
||||||
|
nested={true}
|
||||||
|
{bindings}
|
||||||
|
{componentBindings}
|
||||||
|
/>
|
||||||
|
</span>
|
|
@ -0,0 +1,81 @@
|
||||||
|
<script>
|
||||||
|
import { createEventDispatcher, getContext } from "svelte"
|
||||||
|
import { ActionButton } from "@budibase/bbui"
|
||||||
|
|
||||||
|
const stepState = getContext("step-form-block")
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
$: ({ stepsCount, currentStep } = $stepState)
|
||||||
|
|
||||||
|
const parseLastIdx = stepsCount => {
|
||||||
|
return Math.max(stepsCount - 1, 0)
|
||||||
|
}
|
||||||
|
$: lastIdx = parseLastIdx(stepsCount)
|
||||||
|
|
||||||
|
const stepAction = action => {
|
||||||
|
dispatch("change", {
|
||||||
|
action,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if stepsCount === 1}
|
||||||
|
<ActionButton
|
||||||
|
icon="MultipleAdd"
|
||||||
|
secondary
|
||||||
|
on:click={() => {
|
||||||
|
stepAction("addStep")
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add Step
|
||||||
|
</ActionButton>
|
||||||
|
{:else}
|
||||||
|
<div class="step-actions">
|
||||||
|
<ActionButton
|
||||||
|
size="S"
|
||||||
|
secondary
|
||||||
|
icon="ChevronLeft"
|
||||||
|
disabled={currentStep === 0}
|
||||||
|
on:click={() => {
|
||||||
|
stepAction("previousStep")
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ActionButton
|
||||||
|
size="S"
|
||||||
|
secondary
|
||||||
|
disabled={currentStep === lastIdx}
|
||||||
|
icon="ChevronRight"
|
||||||
|
on:click={() => {
|
||||||
|
stepAction("nextStep")
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ActionButton
|
||||||
|
size="S"
|
||||||
|
secondary
|
||||||
|
icon="Close"
|
||||||
|
disabled={stepsCount === 1}
|
||||||
|
on:click={() => {
|
||||||
|
stepAction("removeStep")
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ActionButton
|
||||||
|
size="S"
|
||||||
|
secondary
|
||||||
|
icon="MultipleAdd"
|
||||||
|
on:click={() => {
|
||||||
|
stepAction("addStep")
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.step-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-actions :global(.spectrum-ActionButton) {
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -24,6 +24,7 @@
|
||||||
export let propertyFocus = false
|
export let propertyFocus = false
|
||||||
export let info = null
|
export let info = null
|
||||||
export let disableBindings = false
|
export let disableBindings = false
|
||||||
|
export let wide
|
||||||
|
|
||||||
$: nullishValue = value == null || value === ""
|
$: nullishValue = value == null || value === ""
|
||||||
$: allBindings = getAllBindings(bindings, componentBindings, nested)
|
$: allBindings = getAllBindings(bindings, componentBindings, nested)
|
||||||
|
@ -78,7 +79,7 @@
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="property-control"
|
class="property-control"
|
||||||
class:wide={!label || labelHidden}
|
class:wide={!label || labelHidden || wide === true}
|
||||||
class:highlighted={highlighted && nullishValue}
|
class:highlighted={highlighted && nullishValue}
|
||||||
class:property-focus={propertyFocus}
|
class:property-focus={propertyFocus}
|
||||||
>
|
>
|
||||||
|
@ -104,6 +105,7 @@
|
||||||
{...props}
|
{...props}
|
||||||
on:drawerHide
|
on:drawerHide
|
||||||
on:drawerShow
|
on:drawerShow
|
||||||
|
on:meta
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{#if info}
|
{#if info}
|
||||||
|
@ -147,7 +149,16 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
.property-control.wide .control {
|
.property-control.wide .control {
|
||||||
grid-column: 1 / -1;
|
flex: 1;
|
||||||
|
}
|
||||||
|
.property-control.wide {
|
||||||
|
grid-template-columns: unset;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.property-control.wide > * {
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
.text {
|
.text {
|
||||||
font-size: var(--spectrum-global-dimension-font-size-75);
|
font-size: var(--spectrum-global-dimension-font-size-75);
|
||||||
|
|
|
@ -15,9 +15,12 @@
|
||||||
export let componentBindings
|
export let componentBindings
|
||||||
export let isScreen = false
|
export let isScreen = false
|
||||||
export let onUpdateSetting
|
export let onUpdateSetting
|
||||||
|
export let getCustomComponent
|
||||||
|
export let getCustomSectionTitle
|
||||||
export let showSectionTitle = true
|
export let showSectionTitle = true
|
||||||
export let includeHidden = false
|
export let includeHidden = false
|
||||||
export let tag
|
export let tag
|
||||||
|
export let noPadding = false
|
||||||
|
|
||||||
$: sections = getSections(
|
$: sections = getSections(
|
||||||
componentInstance,
|
componentInstance,
|
||||||
|
@ -129,13 +132,30 @@
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resolveComponentByType = setting => {
|
||||||
|
if (setting.type) {
|
||||||
|
return getComponentForSetting(setting)
|
||||||
|
} else if (setting.customType && typeof getCustomComponent === "function") {
|
||||||
|
return getCustomComponent(setting.customType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolveSectionName = section => {
|
||||||
|
console.log(resolveSectionName)
|
||||||
|
if (typeof getCustomSectionTitle === "function") {
|
||||||
|
return getCustomSectionTitle(section)
|
||||||
|
} else {
|
||||||
|
return section.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const canRenderControl = (instance, setting, isScreen, includeHidden) => {
|
const canRenderControl = (instance, setting, isScreen, includeHidden) => {
|
||||||
// Prevent rendering on click setting for screens
|
// Prevent rendering on click setting for screens
|
||||||
if (setting?.type === "event" && isScreen) {
|
if (setting?.type === "event" && isScreen) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// Check we have a component to render for this setting
|
// Check we have a component to render for this setting
|
||||||
const control = getComponentForSetting(setting)
|
const control = resolveComponentByType(setting)
|
||||||
if (!control) {
|
if (!control) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -152,6 +172,7 @@
|
||||||
<DetailSummary
|
<DetailSummary
|
||||||
name={showSectionTitle ? section.name : ""}
|
name={showSectionTitle ? section.name : ""}
|
||||||
show={section.collapsed !== true}
|
show={section.collapsed !== true}
|
||||||
|
{noPadding}
|
||||||
>
|
>
|
||||||
{#if section.info}
|
{#if section.info}
|
||||||
<div class="section-info">
|
<div class="section-info">
|
||||||
|
@ -168,9 +189,10 @@
|
||||||
{#if setting.visible}
|
{#if setting.visible}
|
||||||
<PropertyControl
|
<PropertyControl
|
||||||
type={setting.type}
|
type={setting.type}
|
||||||
control={getComponentForSetting(setting)}
|
control={resolveComponentByType(setting)}
|
||||||
label={setting.label}
|
label={setting.label}
|
||||||
labelHidden={setting.labelHidden}
|
labelHidden={setting.labelHidden}
|
||||||
|
wide={setting.wide}
|
||||||
key={setting.key}
|
key={setting.key}
|
||||||
value={componentInstance[setting.key]}
|
value={componentInstance[setting.key]}
|
||||||
defaultValue={setting.defaultValue}
|
defaultValue={setting.defaultValue}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
"cardsblock",
|
"cardsblock",
|
||||||
"repeaterblock",
|
"repeaterblock",
|
||||||
"formblock",
|
"formblock",
|
||||||
|
"multistepformblock",
|
||||||
"chartblock",
|
"chartblock",
|
||||||
"rowexplorer"
|
"rowexplorer"
|
||||||
]
|
]
|
||||||
|
|
|
@ -6058,10 +6058,14 @@
|
||||||
{
|
{
|
||||||
"type": "stepConfiguration",
|
"type": "stepConfiguration",
|
||||||
"key": "steps",
|
"key": "steps",
|
||||||
"nested": true
|
"nested": true,
|
||||||
|
"labelHidden": true,
|
||||||
|
"resetOn": ["dataSource", "actionType"],
|
||||||
|
"defaultValue": [
|
||||||
|
{ "title": null, "desc": null, "fields": null, "buttons": {} }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"resetOn": ["dataSource", "actionType"]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"actions": [
|
"actions": [
|
||||||
|
|
|
@ -0,0 +1,166 @@
|
||||||
|
<script>
|
||||||
|
import BlockComponent from "components/BlockComponent.svelte"
|
||||||
|
import Block from "components/Block.svelte"
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
import { builderStore } from "stores"
|
||||||
|
|
||||||
|
export let componentInstance
|
||||||
|
export let steps
|
||||||
|
export let dataSource
|
||||||
|
export let initialFormStep = 1
|
||||||
|
|
||||||
|
const { fetchDatasourceSchema } = getContext("sdk")
|
||||||
|
const component = getContext("component")
|
||||||
|
|
||||||
|
let schema
|
||||||
|
let formId
|
||||||
|
|
||||||
|
const getCurrentStep = () => {
|
||||||
|
if ($builderStore?.component?._id === $component.id) {
|
||||||
|
return $builderStore?.component.step
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
$: currentStep = getCurrentStep(
|
||||||
|
$builderStore?.component?._id,
|
||||||
|
componentInstance
|
||||||
|
)
|
||||||
|
|
||||||
|
$: fetchSchema(dataSource)
|
||||||
|
|
||||||
|
const getPropsForField = field => {
|
||||||
|
let fieldProps = field._component
|
||||||
|
? {
|
||||||
|
...field,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
field: field.name,
|
||||||
|
label: field.name,
|
||||||
|
placeholder: field.name,
|
||||||
|
_instanceName: field.name,
|
||||||
|
}
|
||||||
|
return fieldProps
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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",
|
||||||
|
bb_reference: "bbreferencefield",
|
||||||
|
}
|
||||||
|
|
||||||
|
const getComponentForField = field => {
|
||||||
|
const fieldSchemaName = field.field || field.name
|
||||||
|
if (!fieldSchemaName || !schema?.[fieldSchemaName]) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const type = schema[fieldSchemaName].type
|
||||||
|
return FieldTypeToComponentMap[type]
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchSchema = async () => {
|
||||||
|
schema = (await fetchDatasourceSchema(dataSource)) || {}
|
||||||
|
}
|
||||||
|
|
||||||
|
$: stepProps = steps?.map(step => {
|
||||||
|
const { title, desc, fields, buttons } = step
|
||||||
|
return {
|
||||||
|
fields: getDefaultFields(fields || [], schema),
|
||||||
|
title,
|
||||||
|
desc,
|
||||||
|
buttons,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#key stepProps}
|
||||||
|
<Block>
|
||||||
|
<BlockComponent
|
||||||
|
type="form"
|
||||||
|
props={{
|
||||||
|
dataSource,
|
||||||
|
initialFormStep,
|
||||||
|
step: $builderStore.inBuilder === true ? currentStep + 1 : null,
|
||||||
|
}}
|
||||||
|
context="form"
|
||||||
|
bind:id={formId}
|
||||||
|
>
|
||||||
|
{#each steps || [] as step, idx ("step" + step._id)}
|
||||||
|
<BlockComponent
|
||||||
|
type="formstep"
|
||||||
|
props={{ step: idx + 1, _instanceName: `Step ${idx + 1}` }}
|
||||||
|
>
|
||||||
|
<BlockComponent
|
||||||
|
type="container"
|
||||||
|
props={{
|
||||||
|
gap: "M",
|
||||||
|
direction: "column",
|
||||||
|
hAlign: "stretch",
|
||||||
|
vAlign: "top",
|
||||||
|
size: "shrink",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<BlockComponent type="container">
|
||||||
|
<BlockComponent
|
||||||
|
type="heading"
|
||||||
|
props={{ text: stepProps?.[idx]?.title }}
|
||||||
|
/>
|
||||||
|
</BlockComponent>
|
||||||
|
|
||||||
|
<BlockComponent
|
||||||
|
type="text"
|
||||||
|
props={{ text: stepProps?.[idx]?.desc }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<BlockComponent type="fieldgroup">
|
||||||
|
{#each stepProps?.[idx]?.fields || [] as field, fieldIdx ("field_" + fieldIdx)}
|
||||||
|
{#if getComponentForField(field) && field.active}
|
||||||
|
<BlockComponent
|
||||||
|
type={getComponentForField(field)}
|
||||||
|
props={getPropsForField(field)}
|
||||||
|
order={idx}
|
||||||
|
interactive
|
||||||
|
name={field?.field}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</BlockComponent>
|
||||||
|
<BlockComponent
|
||||||
|
type="buttongroup"
|
||||||
|
props={{ buttons: stepProps?.[idx]?.buttons }}
|
||||||
|
/>
|
||||||
|
</BlockComponent>
|
||||||
|
</BlockComponent>
|
||||||
|
{/each}
|
||||||
|
</BlockComponent>
|
||||||
|
</Block>
|
||||||
|
{/key}
|
|
@ -4,3 +4,4 @@ export { default as repeaterblock } from "./RepeaterBlock.svelte"
|
||||||
export { default as formblock } from "./form/FormBlock.svelte"
|
export { default as formblock } from "./form/FormBlock.svelte"
|
||||||
export { default as chartblock } from "./ChartBlock.svelte"
|
export { default as chartblock } from "./ChartBlock.svelte"
|
||||||
export { default as rowexplorer } from "./RowExplorer.svelte"
|
export { default as rowexplorer } from "./RowExplorer.svelte"
|
||||||
|
export { default as multistepformblock } from "./MultiStepFormblock.svelte"
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let actionType = "Create"
|
export let actionType = "Create"
|
||||||
export let initialFormStep = 1
|
export let initialFormStep = 1
|
||||||
|
export let step
|
||||||
|
|
||||||
// Not exposed as a builder setting. Used internally to disable validation
|
// Not exposed as a builder setting. Used internally to disable validation
|
||||||
// for fields rendered in things like search blocks.
|
// for fields rendered in things like search blocks.
|
||||||
|
@ -36,6 +37,15 @@
|
||||||
let table
|
let table
|
||||||
let currentStep = writable(getInitialFormStep())
|
let currentStep = writable(getInitialFormStep())
|
||||||
|
|
||||||
|
$: if (
|
||||||
|
currentStep &&
|
||||||
|
Number.isInteger($currentStep) &&
|
||||||
|
Number.isInteger(step) &&
|
||||||
|
step !== currentStep
|
||||||
|
) {
|
||||||
|
currentStep.set(step)
|
||||||
|
}
|
||||||
|
|
||||||
$: fetchSchema(dataSource)
|
$: fetchSchema(dataSource)
|
||||||
$: schemaKey = generateSchemaKey(schema)
|
$: schemaKey = generateSchemaKey(schema)
|
||||||
$: initialValues = getInitialValues(actionType, dataSource, $context)
|
$: initialValues = getInitialValues(actionType, dataSource, $context)
|
||||||
|
|
|
@ -75,6 +75,11 @@ const loadBudibase = async () => {
|
||||||
} else {
|
} else {
|
||||||
dndStore.actions.reset()
|
dndStore.actions.reset()
|
||||||
}
|
}
|
||||||
|
} else if ("builder-meta") {
|
||||||
|
builderStore.update(state => ({
|
||||||
|
...state,
|
||||||
|
...data,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue