diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index 5aecd4aff7..47a02518cb 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -354,6 +354,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 => { @@ -895,7 +905,12 @@ export const getFrontendStore = () => { component[name] = value }) }, - ejectBlock: async (componentId, ejectedDefinition) => { + requestEjectBlock: componentId => { + store.actions.preview.sendEvent("eject-block", componentId) + }, + handleEjectBlock: async (componentId, ejectedDefinition) => { + let nextSelectedComponentId + await store.actions.screens.patch(screen => { const parent = findComponentParent(screen.props, componentId) @@ -908,8 +923,18 @@ export const getFrontendStore = () => { const childIndex = parent._children.findIndex( child => child._id === componentId ) + makeComponentUnique(ejectedDefinition) parent._children[childIndex] = ejectedDefinition + nextSelectedComponentId = ejectedDefinition._id }) + + // Select new root component + if (nextSelectedComponentId) { + store.update(state => { + state.selectedComponentId = nextSelectedComponentId + return state + }) + } }, }, links: { 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 eb0809f55b..3cbf1c639f 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 @@ -96,11 +96,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,7 +208,7 @@ } } else if (type === "eject-block") { const { id, definition } = data - await store.actions.components.ejectBlock(id, definition) + await store.actions.components.handleEjectBlock(id, definition) } else { console.warn(`Client sent unknown event type: ${type}`) } diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentListPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentListPanel.svelte index b00a0fc94f..488f7fe587 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentListPanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentListPanel.svelte @@ -112,9 +112,9 @@ } else if (e.key === "Enter") { e.preventDefault() $goto("./new") - } else if (e.key === "Enter") { + } else if (e.key === "e") { e.preventDefault() - await store.actions.components.ejectBlock() + await store.actions.components.requestEjectBlock(component?._id) } } else if (e.key === "Backspace" || e.key === "Delete") { // Don't show confirmation for the screen itself diff --git a/packages/client/src/components/Block.svelte b/packages/client/src/components/Block.svelte index 10da66dc39..4c2c6b16c0 100644 --- a/packages/client/src/components/Block.svelte +++ b/packages/client/src/components/Block.svelte @@ -1,6 +1,7 @@ diff --git a/packages/client/src/index.js b/packages/client/src/index.js index b582dab4d3..536d4abb3f 100644 --- a/packages/client/src/index.js +++ b/packages/client/src/index.js @@ -1,5 +1,5 @@ import ClientApp from "./components/ClientApp.svelte" -import { builderStore, appStore, devToolsStore } from "./stores" +import { builderStore, appStore, devToolsStore, blockStore } from "./stores" import loadSpectrumIcons from "@budibase/bbui/spectrum-icons-rollup.js" import { get } from "svelte/store" @@ -32,6 +32,17 @@ const loadBudibase = () => { const enableDevTools = !get(builderStore).inBuilder && get(appStore).isDevApp devToolsStore.actions.setEnabled(enableDevTools) + // Register handler for runtime events from the builder + window.handleBuilderRuntimeEvent = (name, payload) => { + if (!window["##BUDIBASE_IN_BUILDER##"]) { + return + } + if (name === "eject-block") { + const block = blockStore.actions.getBlock(payload) + block?.eject() + } + } + // Create app if one hasn't been created yet if (!app) { app = new ClientApp({ diff --git a/packages/client/src/stores/blocks.js b/packages/client/src/stores/blocks.js new file mode 100644 index 0000000000..98381ec79b --- /dev/null +++ b/packages/client/src/stores/blocks.js @@ -0,0 +1,34 @@ +import { get, writable } from "svelte/store" + +const createBlockStore = () => { + const store = writable({}) + + const registerBlock = (id, instance) => { + store.update(state => ({ + ...state, + [id]: instance, + })) + } + + const unregisterBlock = id => { + store.update(state => { + delete state[id] + return state + }) + } + + const getBlock = id => { + return get(store)[id] + } + + return { + subscribe: store.subscribe, + actions: { + registerBlock, + unregisterBlock, + getBlock, + }, + } +} + +export const blockStore = createBlockStore() diff --git a/packages/client/src/stores/index.js b/packages/client/src/stores/index.js index e28fbaee42..6a72f59db9 100644 --- a/packages/client/src/stores/index.js +++ b/packages/client/src/stores/index.js @@ -17,6 +17,7 @@ export { devToolsStore } from "./devTools" export { componentStore } from "./components" export { uploadStore } from "./uploads.js" export { rowSelectionStore } from "./rowSelection.js" +export { blockStore } from "./blocks.js" // Context stores are layered and duplicated, so it is not a singleton export { createContextStore } from "./context" diff --git a/packages/server/src/api/controllers/static/templates/preview.hbs b/packages/server/src/api/controllers/static/templates/preview.hbs index 28908df507..df9384c1d0 100644 --- a/packages/server/src/api/controllers/static/templates/preview.hbs +++ b/packages/server/src/api/controllers/static/templates/preview.hbs @@ -56,6 +56,16 @@ return } + // If this is a custom event, try and handle it + if (parsed.runtimeEvent) { + const { name, payload } = parsed + if (window.handleBuilderRuntimeEvent) { + window.handleBuilderRuntimeEvent(name, payload) + } + return + } + + // Otherwise this is a full reload message // Extract data from message const { selectedComponentId,