From fe169e8a13cf2e6b83fc5831ff8851d1d0462b30 Mon Sep 17 00:00:00 2001 From: Dean Date: Thu, 30 Nov 2023 11:17:31 +0000 Subject: [PATCH 01/38] Merge commit --- .../design/settings/componentSettings.js | 2 ++ packages/client/manifest.json | 31 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/packages/builder/src/components/design/settings/componentSettings.js b/packages/builder/src/components/design/settings/componentSettings.js index c2bd08760a..f20f1aaeec 100644 --- a/packages/builder/src/components/design/settings/componentSettings.js +++ b/packages/builder/src/components/design/settings/componentSettings.js @@ -25,6 +25,7 @@ import BarButtonList from "./controls/BarButtonList.svelte" import FieldConfiguration from "./controls/FieldConfiguration/FieldConfiguration.svelte" import ButtonConfiguration from "./controls/ButtonConfiguration/ButtonConfiguration.svelte" import RelationshipFilterEditor from "./controls/RelationshipFilterEditor.svelte" +import FormStepConfiguration from "./controls/FormStepConfiguration.svelte" const componentMap = { text: DrawerBindableInput, @@ -50,6 +51,7 @@ const componentMap = { url: URLSelect, fieldConfiguration: FieldConfiguration, buttonConfiguration: ButtonConfiguration, + stepConfiguration: FormStepConfiguration, columns: ColumnEditor, "columns/basic": BasicColumnEditor, "columns/grid": GridColumnEditor, diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 07645d874a..d71156f7fc 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -6017,6 +6017,37 @@ } ] }, + "multistepformblock": { + "name": "Multi step Formblock", + "icon": "AssetsAdded", + "block": true, + "settings": [ + { + "type": "table", + "label": "Data", + "key": "dataSource" + }, + { + "type": "radio", + "label": "Type", + "key": "actionType", + "options": ["Create", "Update", "View"], + "defaultValue": "Create" + }, + { + "name": "Details", + "section": true, + "settings": [ + { + "type": "stepConfiguration", + "key": "steps", + "label": "Multi Steps" + } + ], + "resetOn": ["dataSource", "actionType"] + } + ] + }, "formblock": { "name": "Form Block", "icon": "Form", From 4785ccb49f4b2d982361b1a5926fae337821f3a7 Mon Sep 17 00:00:00 2001 From: Dean Date: Tue, 5 Dec 2023 16:14:48 +0000 Subject: [PATCH 02/38] Merge commit --- packages/client/manifest.json | 56 ++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 9e11ef3084..83554854a3 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -6037,6 +6037,7 @@ "name": "Multi step Formblock", "icon": "AssetsAdded", "block": true, + "hasChildren": false, "settings": [ { "type": "table", @@ -6057,11 +6058,64 @@ { "type": "stepConfiguration", "key": "steps", - "label": "Multi Steps" + "nested": true } ], "resetOn": ["dataSource", "actionType"] } + ], + "actions": [ + { + "type": "ValidateForm", + "suffix": "form" + }, + { + "type": "ClearForm", + "suffix": "form" + }, + { + "type": "UpdateFieldValue", + "suffix": "form" + }, + { + "type": "ScrollTo", + "suffix": "form" + }, + { + "type": "ChangeFormStep", + "suffix": "form" + } + ], + "context": [ + { + "type": "form", + "suffix": "form" + }, + { + "type": "static", + "values": [ + { + "label": "Value", + "key": "__value", + "type": "object" + }, + { + "label": "Valid", + "key": "__valid", + "type": "boolean" + }, + { + "label": "Current Step", + "key": "__currentStep", + "type": "number" + }, + { + "label": "Current Step Valid", + "key": "__currentStepValid", + "type": "boolean" + } + ] + } ] }, "formblock": { From 1a03d9d729ef5c9acfeecab8b01a43aa948fb9ec Mon Sep 17 00:00:00 2001 From: Dean Date: Wed, 6 Dec 2023 20:39:35 +0000 Subject: [PATCH 03/38] Initial pass at the multistep form block --- .../src/DetailSummary/DetailSummary.svelte | 6 + .../ButtonConfiguration.svelte | 3 + .../FieldConfiguration.svelte | 5 +- .../controls/FormStepConfiguration.svelte | 274 ++++++++++++++++++ .../settings/controls/FormStepControls.svelte | 81 ++++++ .../settings/controls/PropertyControl.svelte | 15 +- .../Component/ComponentSettingsSection.svelte | 26 +- .../new/_components/componentStructure.json | 1 + packages/client/manifest.json | 10 +- .../app/blocks/MultiStepFormblock.svelte | 166 +++++++++++ .../client/src/components/app/blocks/index.js | 1 + .../src/components/app/forms/Form.svelte | 10 + packages/client/src/index.js | 5 + 13 files changed, 595 insertions(+), 8 deletions(-) create mode 100644 packages/builder/src/components/design/settings/controls/FormStepConfiguration.svelte create mode 100644 packages/builder/src/components/design/settings/controls/FormStepControls.svelte create mode 100644 packages/client/src/components/app/blocks/MultiStepFormblock.svelte diff --git a/packages/bbui/src/DetailSummary/DetailSummary.svelte b/packages/bbui/src/DetailSummary/DetailSummary.svelte index daa9f3f5ca..808e12b6c4 100644 --- a/packages/bbui/src/DetailSummary/DetailSummary.svelte +++ b/packages/bbui/src/DetailSummary/DetailSummary.svelte @@ -5,6 +5,7 @@ export let name export let show = false export let collapsible = true + export let noPadding = false const dispatch = createEventDispatcher() const onHeaderClick = () => { @@ -31,6 +32,7 @@ class="property-panel" class:show={show || !collapsible} class:no-title={!name} + class:no-padding={noPadding} > @@ -84,6 +86,10 @@ padding: var(--spacing-xl); } + .property-panel.no-title.no-padding { + padding: 0px; + } + .show { display: flex; flex-direction: column; diff --git a/packages/builder/src/components/design/settings/controls/ButtonConfiguration/ButtonConfiguration.svelte b/packages/builder/src/components/design/settings/controls/ButtonConfiguration/ButtonConfiguration.svelte index ce91c8f7b5..63bfecf386 100644 --- a/packages/builder/src/components/design/settings/controls/ButtonConfiguration/ButtonConfiguration.svelte +++ b/packages/builder/src/components/design/settings/controls/ButtonConfiguration/ButtonConfiguration.svelte @@ -34,6 +34,9 @@ $: canAddButtons = max == null || buttonList.length < max const sanitizeValue = val => { + if (!Array.isArray(val)) { + return null + } return val?.map(button => { return button._component ? button : buildPseudoInstance(button) }) diff --git a/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte b/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte index 7f1ac1cf25..4d436a4679 100644 --- a/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte +++ b/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte @@ -39,7 +39,10 @@ ) } - $: datasource = getDatasourceForProvider($currentAsset, componentInstance) + $: datasource = + componentInstance.dataSource || + getDatasourceForProvider($currentAsset, componentInstance) + $: resourceId = datasource?.resourceId || datasource?.tableId $: if (!isEqual(value, cachedValue)) { diff --git a/packages/builder/src/components/design/settings/controls/FormStepConfiguration.svelte b/packages/builder/src/components/design/settings/controls/FormStepConfiguration.svelte new file mode 100644 index 0000000000..dbaefaac96 --- /dev/null +++ b/packages/builder/src/components/design/settings/controls/FormStepConfiguration.svelte @@ -0,0 +1,274 @@ + + + + { + 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} + /> + diff --git a/packages/builder/src/components/design/settings/controls/FormStepControls.svelte b/packages/builder/src/components/design/settings/controls/FormStepControls.svelte new file mode 100644 index 0000000000..9aa6b43d10 --- /dev/null +++ b/packages/builder/src/components/design/settings/controls/FormStepControls.svelte @@ -0,0 +1,81 @@ + + +{#if stepsCount === 1} + { + stepAction("addStep") + }} + > + Add Step + +{:else} +
+ { + stepAction("previousStep") + }} + /> + { + stepAction("nextStep") + }} + /> + { + stepAction("removeStep") + }} + /> + { + stepAction("addStep") + }} + /> +
+{/if} + + diff --git a/packages/builder/src/components/design/settings/controls/PropertyControl.svelte b/packages/builder/src/components/design/settings/controls/PropertyControl.svelte index a6f3d1b218..78f299f237 100644 --- a/packages/builder/src/components/design/settings/controls/PropertyControl.svelte +++ b/packages/builder/src/components/design/settings/controls/PropertyControl.svelte @@ -24,6 +24,7 @@ export let propertyFocus = false export let info = null export let disableBindings = false + export let wide $: nullishValue = value == null || value === "" $: allBindings = getAllBindings(bindings, componentBindings, nested) @@ -78,7 +79,7 @@
@@ -104,6 +105,7 @@ {...props} on:drawerHide on:drawerShow + on:meta />
{#if info} @@ -147,7 +149,16 @@ position: relative; } .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 { font-size: var(--spectrum-global-dimension-font-size-75); diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte index 6093d2a45e..6bc5f33398 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte @@ -15,9 +15,12 @@ export let componentBindings export let isScreen = false export let onUpdateSetting + export let getCustomComponent + export let getCustomSectionTitle export let showSectionTitle = true export let includeHidden = false export let tag + export let noPadding = false $: sections = getSections( 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) => { // Prevent rendering on click setting for screens if (setting?.type === "event" && isScreen) { return false } // Check we have a component to render for this setting - const control = getComponentForSetting(setting) + const control = resolveComponentByType(setting) if (!control) { return false } @@ -152,6 +172,7 @@ {#if section.info} @@ -83,10 +81,6 @@ padding: var(--spacing-xl); } - .property-panel.no-title.no-padding { - padding: 0px; - } - .show { display: flex; flex-direction: column; diff --git a/packages/builder/src/components/design/settings/componentSettings.js b/packages/builder/src/components/design/settings/componentSettings.js index 4228ff2b1e..f2d1520878 100644 --- a/packages/builder/src/components/design/settings/componentSettings.js +++ b/packages/builder/src/components/design/settings/componentSettings.js @@ -26,6 +26,7 @@ import FieldConfiguration from "./controls/FieldConfiguration/FieldConfiguration import ButtonConfiguration from "./controls/ButtonConfiguration/ButtonConfiguration.svelte" import RelationshipFilterEditor from "./controls/RelationshipFilterEditor.svelte" import FormStepConfiguration from "./controls/FormStepConfiguration.svelte" +import FormStepControls from "components/design/settings/controls/FormStepControls.svelte" const componentMap = { text: DrawerBindableInput, @@ -53,6 +54,7 @@ const componentMap = { fieldConfiguration: FieldConfiguration, buttonConfiguration: ButtonConfiguration, stepConfiguration: FormStepConfiguration, + formStepControls: FormStepControls, columns: ColumnEditor, "columns/basic": BasicColumnEditor, "columns/grid": GridColumnEditor, diff --git a/packages/builder/src/components/design/settings/controls/FormStepConfiguration.svelte b/packages/builder/src/components/design/settings/controls/FormStepConfiguration.svelte index 2c8a7eac67..098c6277fb 100644 --- a/packages/builder/src/components/design/settings/controls/FormStepConfiguration.svelte +++ b/packages/builder/src/components/design/settings/controls/FormStepConfiguration.svelte @@ -4,7 +4,6 @@ import { getDatasourceForProvider } from "builderStore/dataBinding" import { currentAsset, store } from "builderStore" import { Helpers } from "@budibase/bbui" - import FormStepControls from "./FormStepControls.svelte" import { writable } from "svelte/store" import { buildMultiStepFormBlockButtonConfig } from "@budibase/frontend-core/src/utils/utils" @@ -14,61 +13,70 @@ export let bindings const dispatch = createEventDispatcher() - - let stepState = [...(value || [])] - - const stepStore = writable({ - stepsCount: stepState?.length || 1, + const multiStepStore = writable({ + stepCount: value?.length ?? 0, currentStep: 0, }) - setContext("step-form-block", stepStore) + setContext("multi-step-form-block", multiStepStore) - $: ({ currentStep } = $stepStore) - $: if (stepState.length) { - stepStore.update(state => ({ - ...state, - stepsCount: stepState.length || 0, - })) - } + $: currentStep = $multiStepStore.currentStep + $: stepCount = value?.length || 0 + $: multiStepStore.update(state => ({ ...state, stepCount })) $: defaultButtonConfig = buildMultiStepFormBlockButtonConfig({ _id: componentInstance._id, - stepCount: value?.length, - currentStep, + stepCount: stepCount, + currentStep: currentStep, }) + $: dataSource = getDatasourceForProvider($currentAsset, componentInstance) + $: emitCurrentStep(currentStep) + $: sectionName = getSectionName($multiStepStore) + $: stepDef = { + settings: [ + { + section: true, + name: sectionName, + settings: [ + { + type: "formStepControls", + 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, + }, + ], + }, + ], + } - // 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 getSectionName = ({ stepCount, currentStep }) => { + if (stepCount <= 1) { + return "Details" + } + return `Details (Step ${currentStep + 1}/${stepCount})` + } const emitCurrentStep = step => { store.actions.preview.sendEvent("builder-meta", { @@ -79,70 +87,52 @@ }) } - $: 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), + const nextStep = currentStep + 1 + dispatch("change", [ + ...value.slice(0, nextStep), {}, - ...stepState.slice(newStepIdx), - ] - - stepStore.update(state => ({ + ...value.slice(nextStep), + ]) + multiStepStore.update(state => ({ ...state, - currentStep: newStepIdx, + currentStep: nextStep, })) - - 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 => ({ + dispatch("change", value.toSpliced(currentStep, 1)) + const newStep = Math.min(currentStep, stepCount - 2) + multiStepStore.update(state => ({ ...state, - currentStep: targetStepIdx, + currentStep: newStep, })) - - dispatch("change", stepState) - emitCurrentStep(targetStepIdx) } const previousStep = () => { const prevStepIdx = Math.max(currentStep - 1, 0) - stepStore.update(state => ({ + multiStepStore.update(state => ({ ...state, currentStep: prevStepIdx, })) - emitCurrentStep(prevStepIdx) } const nextStep = () => { const nextStepIdx = currentStep + 1 - stepStore.update(state => ({ + multiStepStore.update(state => ({ ...state, - currentStep: Math.min(nextStepIdx, stepState.length - 1), + currentStep: Math.min(nextStepIdx, value.length - 1), })) - emitCurrentStep(nextStepIdx) } const updateStep = (field, val) => { - stepState[currentStep] ||= {} - stepState[currentStep][field.key] = val - dispatch("change", stepState) + const newStep = { + ...value[currentStep], + [field.key]: val, + } + let newValue = value.slice() + newValue[currentStep] = newStep + dispatch("change", newValue) } const handleStepAction = action => { @@ -158,9 +148,6 @@ break case "previousStep": previousStep() - break - default: - console.log("Nothing") } } @@ -186,32 +173,27 @@ } } - $: stepConfigInstance = buildPseudoInstance(stepState?.[currentStep] || {}) + $: stepConfigInstance = buildPseudoInstance(value[currentStep] || {}) - +
{ - 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} /> - +
+ + diff --git a/packages/builder/src/components/design/settings/controls/FormStepControls.svelte b/packages/builder/src/components/design/settings/controls/FormStepControls.svelte index 9aa6b43d10..603b2631f5 100644 --- a/packages/builder/src/components/design/settings/controls/FormStepControls.svelte +++ b/packages/builder/src/components/design/settings/controls/FormStepControls.svelte @@ -2,15 +2,10 @@ import { createEventDispatcher, getContext } from "svelte" import { ActionButton } from "@budibase/bbui" - const stepState = getContext("step-form-block") + const multiStepStore = getContext("multi-step-form-block") const dispatch = createEventDispatcher() - $: ({ stepsCount, currentStep } = $stepState) - - const parseLastIdx = stepsCount => { - return Math.max(stepsCount - 1, 0) - } - $: lastIdx = parseLastIdx(stepsCount) + $: ({ stepCount, currentStep } = $multiStepStore) const stepAction = action => { dispatch("change", { @@ -19,7 +14,7 @@ } -{#if stepsCount === 1} +{#if stepCount === 1} { stepAction("nextStep") @@ -53,7 +48,7 @@ size="S" secondary icon="Close" - disabled={stepsCount === 1} + disabled={stepCount === 1} on:click={() => { stepAction("removeStep") }} diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte index 97a56e1667..f6f4cf9c90 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte @@ -15,12 +15,9 @@ export let componentBindings export let isScreen = false export let onUpdateSetting - export let getCustomComponent - export let getCustomSectionTitle export let showSectionTitle = true export let includeHidden = false export let tag - export let noPadding = false $: sections = getSections( componentInstance, @@ -133,30 +130,13 @@ }) } - 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) => { // Prevent rendering on click setting for screens if (setting?.type === "event" && isScreen) { return false } // Check we have a component to render for this setting - const control = resolveComponentByType(setting) + const control = getComponentForSetting(setting) if (!control) { return false } @@ -172,9 +152,8 @@ {#if section.visible} {#if section.info}