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..aaa0eb0184 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -85,6 +85,7 @@ const INITIAL_FRONTEND_STATE = { selectedScreenId: null, selectedComponentId: null, selectedLayoutId: null, + hoverComponentId: null, // Client state selectedComponentInstance: null, @@ -112,7 +113,7 @@ export const getFrontendStore = () => { } let clone = cloneDeep(screen) const result = patchFn(clone) - + // An explicit false result means skip this change if (result === false) { return } @@ -601,6 +602,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 +703,6 @@ export const getFrontendStore = () => { component[setting.key] = setting.defaultValue } } - // Validate non-empty settings else { if (setting.type === "dataProvider") { @@ -722,6 +752,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 +878,16 @@ export const getFrontendStore = () => { if (!component) { return false } - return patchFn(component, screen) + + // Mutates the fetched component with updates + const patchResult = patchFn(component, screen) + + // Mutates the component with any required settings updates + const migrated = store.actions.components.migrateSettings(component) + + // Returning an explicit false signifies that we should skip this + // update. If we migrated something, ensure we never skip. + return migrated ? null : patchResult } await store.actions.screens.patch(patchScreen, screenId) }, @@ -1247,9 +1289,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 +1317,7 @@ export const getFrontendStore = () => { }) } component[name] = value + return true } }, requestEjectBlock: componentId => { @@ -1278,7 +1325,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/builder/src/components/common/NavItem.svelte b/packages/builder/src/components/common/NavItem.svelte index 35846525af..02cef82e80 100644 --- a/packages/builder/src/components/common/NavItem.svelte +++ b/packages/builder/src/components/common/NavItem.svelte @@ -23,6 +23,7 @@ export let showTooltip = false export let selectedBy = null export let compact = false + export let hovering = false const scrollApi = getContext("scroll") const dispatch = createEventDispatcher() @@ -61,6 +62,7 @@
- + { + e.stopPropagation() + }} + on:change={onToggle(item)} + text="" + value={item.active} + thin + />
diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte index 45fe005ceb..65ea172012 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte @@ -36,12 +36,14 @@ // Determine selected component ID $: selectedComponentId = $store.selectedComponentId + $: hoverComponentId = $store.hoverComponentId $: previewData = { appId: $store.appId, layout, screen, selectedComponentId, + hoverComponentId, theme: $store.theme, customTheme: $store.customTheme, previewDevice: $store.previewDevice, @@ -117,6 +119,8 @@ error = event.error || "An unknown error occurred" } else if (type === "select-component" && data.id) { $store.selectedComponentId = data.id + } else if (type === "hover-component" && data.id) { + $store.hoverComponentId = data.id } else if (type === "update-prop") { await store.actions.components.updateSetting(data.prop, data.value) } else if (type === "update-styles") { diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/ComponentTree.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/ComponentTree.svelte index 1ad522cbd4..8d4d64e4be 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/ComponentTree.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/ComponentTree.svelte @@ -89,6 +89,17 @@ } return findComponentPath($selectedComponent, component._id)?.length > 0 } + + const handleMouseover = componentId => { + if ($store.hoverComponentId !== componentId) { + $store.hoverComponentId = componentId + } + } + const handleMouseout = componentId => { + if ($store.hoverComponentId === componentId) { + $store.hoverComponentId = null + } + }