diff --git a/packages/builder/src/builderStore/index.js b/packages/builder/src/builderStore/index.js index ece17cb46f..dd54dcf13e 100644 --- a/packages/builder/src/builderStore/index.js +++ b/packages/builder/src/builderStore/index.js @@ -8,6 +8,7 @@ import { derived, get } from "svelte/store" import { findComponent, findComponentPath } from "./componentUtils" import { RoleUtils } from "@budibase/frontend-core" import { createHistoryStore } from "builderStore/store/history" +import { cloneDeep } from "lodash/fp" export const store = getFrontendStore() export const automationStore = getAutomationStore() @@ -69,7 +70,14 @@ export const selectedComponent = derived( if (!$selectedScreen || !$store.selectedComponentId) { return null } - return findComponent($selectedScreen?.props, $store.selectedComponentId) + const selected = findComponent( + $selectedScreen?.props, + $store.selectedComponentId + ) + + const clone = selected ? cloneDeep(selected) : selected + store.actions.components.migrateSettings(clone) + return clone } ) diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index a4729b4a8a..7e510c3d26 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -601,6 +601,36 @@ export const getFrontendStore = () => { // Finally try an external table return validTables.find(table => table.sourceType === DB_TYPE_EXTERNAL) }, + migrateSettings: enrichedComponent => { + const componentPrefix = "@budibase/standard-components" + let migrated = false + + if (enrichedComponent?._component == `${componentPrefix}/formblock`) { + // Use default config if the 'buttons' prop has never been initialised + if (!("buttons" in enrichedComponent)) { + enrichedComponent["buttons"] = + Utils.buildDynamicButtonConfig(enrichedComponent) + migrated = true + } else if (enrichedComponent["buttons"] == null) { + // Ignore legacy config if 'buttons' has been reset by 'resetOn' + const { _id, actionType, dataSource } = enrichedComponent + enrichedComponent["buttons"] = Utils.buildDynamicButtonConfig({ + _id, + actionType, + dataSource, + }) + migrated = true + } + + // Ensure existing Formblocks position their buttons at the top. + if (!("buttonPosition" in enrichedComponent)) { + enrichedComponent["buttonPosition"] = "top" + migrated = true + } + } + + return migrated + }, enrichEmptySettings: (component, opts) => { if (!component?._component) { return @@ -672,7 +702,6 @@ export const getFrontendStore = () => { component[setting.key] = setting.defaultValue } } - // Validate non-empty settings else { if (setting.type === "dataProvider") { @@ -722,6 +751,9 @@ export const getFrontendStore = () => { useDefaultValues: true, }) + // Migrate nested component settings + store.actions.components.migrateSettings(instance) + // Add any extra properties the component needs let extras = {} if (definition.hasChildren) { @@ -845,7 +877,13 @@ export const getFrontendStore = () => { if (!component) { return false } - return patchFn(component, screen) + + // Mutates the fetched component with updates + const updated = patchFn(component, screen) + // Mutates the component with any required settings updates + const migrated = store.actions.components.migrateSettings(component) + + return updated || migrated } await store.actions.screens.patch(patchScreen, screenId) }, @@ -1247,9 +1285,13 @@ export const getFrontendStore = () => { const settings = getComponentSettings(component._component) const updatedSetting = settings.find(setting => setting.key === name) - const resetFields = settings.filter( - setting => name === setting.resetOn - ) + // Can be a single string or array of strings + const resetFields = settings.filter(setting => { + return ( + name === setting.resetOn || + (Array.isArray(setting.resetOn) && setting.resetOn.includes(name)) + ) + }) resetFields?.forEach(setting => { component[setting.key] = null }) @@ -1271,6 +1313,7 @@ export const getFrontendStore = () => { }) } component[name] = value + return true } }, requestEjectBlock: componentId => { @@ -1278,7 +1321,6 @@ export const getFrontendStore = () => { }, handleEjectBlock: async (componentId, ejectedDefinition) => { let nextSelectedComponentId - await store.actions.screens.patch(screen => { const block = findComponent(screen.props, componentId) const parent = findComponentParent(screen.props, componentId) diff --git a/packages/client/manifest.json b/packages/client/manifest.json index fdb0ad9db1..3b99ddb7b5 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -6112,54 +6112,32 @@ } ] }, + { + "tag": "style", + "type": "select", + "label": "Button position", + "key": "buttonPosition", + "options": [ + { + "label": "Bottom", + "value": "bottom" + }, + { + "label": "Top", + "value": "top" + } + ], + "defaultValue": "bottom" + }, { "section": true, "name": "Buttons", - "dependsOn": { - "setting": "actionType", - "value": "View", - "invert": true - }, "settings": [ { - "type": "text", - "key": "saveButtonLabel", - "label": "Save button", + "type": "buttonConfiguration", + "key": "buttons", "nested": true, - "defaultValue": "Save" - }, - { - "type": "text", - "key": "deleteButtonLabel", - "label": "Delete button", - "nested": true, - "defaultValue": "Delete", - "dependsOn": { - "setting": "actionType", - "value": "Update" - } - }, - { - "type": "url", - "label": "Navigate after button press", - "key": "actionUrl", - "placeholder": "Choose a screen", - "dependsOn": { - "setting": "actionType", - "value": "View", - "invert": true - } - }, - { - "type": "boolean", - "label": "Hide notifications", - "key": "notificationOverride", - "defaultValue": false, - "dependsOn": { - "setting": "actionType", - "value": "View", - "invert": true - } + "resetOn": ["actionType", "dataSource"] } ] }, diff --git a/packages/client/src/components/app/blocks/TableBlock.svelte b/packages/client/src/components/app/blocks/TableBlock.svelte index 1cb77cb3e5..c8b6a07e3d 100644 --- a/packages/client/src/components/app/blocks/TableBlock.svelte +++ b/packages/client/src/components/app/blocks/TableBlock.svelte @@ -5,6 +5,7 @@ import BlockComponent from "components/BlockComponent.svelte" import { makePropSafe as safe } from "@budibase/string-templates" import { enrichSearchColumns, enrichFilter } from "utils/blocks.js" + import { Utils } from "@budibase/frontend-core" export let title export let dataSource @@ -33,6 +34,7 @@ export let notificationOverride const { fetchDatasourceSchema, API } = getContext("sdk") + const component = getContext("component") const stateKey = `ID_${generate()}` let formId @@ -259,16 +261,25 @@ name="Details form block" type="formblock" bind:id={detailsFormBlockId} + context="form-edit" props={{ dataSource, - saveButtonLabel: sidePanelSaveLabel || "Save", //always show - deleteButtonLabel: deleteLabel, + buttonPosition: "top", + buttons: Utils.buildDynamicButtonConfig({ + _id: $component.id + "-form-edit", + showDeleteButton: deleteLabel !== "", + showSaveButton: true, + saveButtonLabel: sidePanelSaveLabel || "Save", + deleteButtonLabel: deleteLabel, + notificationOverride, + actionType: "Update", + dataSource, + }), actionType: "Update", rowId: `{{ ${safe("state")}.${safe(stateKey)} }}`, fields: sidePanelFields || normalFields, title: editTitle, labelPosition: "left", - notificationOverride, }} /> @@ -284,16 +295,23 @@ diff --git a/packages/client/src/components/app/blocks/form/FormBlock.svelte b/packages/client/src/components/app/blocks/form/FormBlock.svelte index e4d3b55eff..f23ecf451d 100644 --- a/packages/client/src/components/app/blocks/form/FormBlock.svelte +++ b/packages/client/src/components/app/blocks/form/FormBlock.svelte @@ -4,28 +4,31 @@ import Block from "components/Block.svelte" import { makePropSafe as safe } from "@budibase/string-templates" import InnerFormBlock from "./InnerFormBlock.svelte" + import { Utils } from "@budibase/frontend-core" export let actionType export let dataSource export let size export let disabled export let fields + export let buttons + export let buttonPosition + export let title export let description - export let showDeleteButton - export let showSaveButton - export let saveButtonLabel - export let deleteButtonLabel export let rowId export let actionUrl export let noRowsMessage export let notificationOverride - // Accommodate old config to ensure delete button does not reappear - $: deleteLabel = showDeleteButton === false ? "" : deleteButtonLabel?.trim() - $: saveLabel = showSaveButton === false ? "" : saveButtonLabel?.trim() + // Legacy + export let showDeleteButton + export let showSaveButton + export let saveButtonLabel + export let deleteButtonLabel const { fetchDatasourceSchema } = getContext("sdk") + const component = getContext("component") const convertOldFieldFormat = fields => { if (!fields) { @@ -98,11 +101,23 @@ fields: fieldsOrDefault, title, description, - saveButtonLabel: saveLabel, - deleteButtonLabel: deleteLabel, schema, repeaterId, notificationOverride, + buttons: + buttons || + Utils.buildDynamicButtonConfig({ + _id: $component.id, + showDeleteButton, + showSaveButton, + saveButtonLabel, + deleteButtonLabel, + notificationOverride, + actionType, + actionUrl, + dataSource, + }), + buttonPosition: buttons ? buttonPosition : "top", } const fetchSchema = async () => { schema = (await fetchDatasourceSchema(dataSource)) || {} diff --git a/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte b/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte index 52ef3ac80c..24d4cfa14c 100644 --- a/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte +++ b/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte @@ -1,22 +1,18 @@