diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js
index aefdba9fb2..b1365c3ece 100644
--- a/packages/builder/src/builderStore/store/frontend.js
+++ b/packages/builder/src/builderStore/store/frontend.js
@@ -330,6 +330,16 @@ export const getFrontendStore = () => {
return state
})
},
+ sendEvent: (name, payload) => {
+ const { previewEventHandler } = get(store)
+ previewEventHandler?.(name, payload)
+ },
+ registerEventHandler: handler => {
+ store.update(state => {
+ state.previewEventHandler = handler
+ return state
+ })
+ },
},
layouts: {
select: layoutId => {
@@ -891,6 +901,50 @@ export const getFrontendStore = () => {
component[name] = value
})
},
+ requestEjectBlock: componentId => {
+ store.actions.preview.sendEvent("eject-block", componentId)
+ },
+ handleEjectBlock: async (componentId, ejectedDefinition) => {
+ let nextSelectedComponentId
+
+ await store.actions.screens.patch(screen => {
+ const block = findComponent(screen.props, componentId)
+ const parent = findComponentParent(screen.props, componentId)
+
+ // Sanity check
+ if (!block || !parent?._children?.length) {
+ return false
+ }
+
+ // Attach block children back into ejected definition, using the
+ // _containsSlot flag to know where to insert them
+ const slotContainer = findAllMatchingComponents(
+ ejectedDefinition,
+ x => x._containsSlot
+ )[0]
+ if (slotContainer) {
+ delete slotContainer._containsSlot
+ slotContainer._children = [
+ ...(slotContainer._children || []),
+ ...(block._children || []),
+ ]
+ }
+
+ // Replace block with ejected definition
+ makeComponentUnique(ejectedDefinition)
+ const index = parent._children.findIndex(x => x._id === componentId)
+ parent._children[index] = ejectedDefinition
+ nextSelectedComponentId = ejectedDefinition._id
+ })
+
+ // Select new root component
+ if (nextSelectedComponentId) {
+ store.update(state => {
+ state.selectedComponentId = nextSelectedComponentId
+ return state
+ })
+ }
+ },
},
links: {
save: async (url, title) => {
diff --git a/packages/builder/src/components/design/settings/controls/EjectBlockButton.svelte b/packages/builder/src/components/design/settings/controls/EjectBlockButton.svelte
new file mode 100644
index 0000000000..e19d4b584b
--- /dev/null
+++ b/packages/builder/src/components/design/settings/controls/EjectBlockButton.svelte
@@ -0,0 +1,13 @@
+
+
+
diff --git a/packages/builder/src/components/design/settings/controls/PropertyControl.svelte b/packages/builder/src/components/design/settings/controls/PropertyControl.svelte
index 3927e0b3a5..8be770e3a0 100644
--- a/packages/builder/src/components/design/settings/controls/PropertyControl.svelte
+++ b/packages/builder/src/components/design/settings/controls/PropertyControl.svelte
@@ -20,6 +20,7 @@
export let componentBindings = []
export let nested = false
export let highlighted = false
+ export let info = null
$: nullishValue = value == null || value === ""
$: allBindings = getAllBindings(bindings, componentBindings, nested)
@@ -99,6 +100,9 @@
{...props}
/>
+ {#if info}
+ {@html info}
+ {/if}
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 9f21a6a29f..309b676a70 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
@@ -98,11 +98,21 @@
`./components/${$selectedComponent?._id}/new`
)
+ // Register handler to send custom to the preview
+ $: store.actions.preview.registerEventHandler((name, payload) => {
+ iframe?.contentWindow.postMessage(
+ JSON.stringify({
+ name,
+ payload,
+ isBudibaseEvent: true,
+ runtimeEvent: true,
+ })
+ )
+ })
+
// Update the iframe with the builder info to render the correct preview
const refreshContent = message => {
- if (iframe) {
- iframe.contentWindow.postMessage(message)
- }
+ iframe?.contentWindow.postMessage(message)
}
const receiveMessage = message => {
@@ -198,6 +208,9 @@
block: "center",
})
}
+ } else if (type === "eject-block") {
+ const { id, definition } = data
+ await store.actions.components.handleEjectBlock(id, definition)
} else if (type === "reload-plugin") {
await store.actions.components.refreshDefinitions()
} else {
diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentDropdownMenu.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentDropdownMenu.svelte
index c19cba1aac..aeaa577455 100644
--- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentDropdownMenu.svelte
+++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentDropdownMenu.svelte
@@ -4,7 +4,9 @@
export let component
+ $: definition = store.actions.components.getDefinition(component?._component)
$: noPaste = !$store.componentToPaste
+ $: isBlock = definition?.block === true
const keyboardEvent = (key, ctrlKey = false) => {
document.dispatchEvent(
@@ -30,6 +32,15 @@
>
Delete
+ {#if isBlock}
+
+ {/if}