diff --git a/packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte b/packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte index d2d67bf99f..99de9f7942 100644 --- a/packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte +++ b/packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte @@ -32,6 +32,16 @@ .component("@budibase/standard-components/screenslot") .instanceName("Content Placeholder") .json() + + // Messages that can be sent from the iframe preview to the builder + // Budibase events are and initalisation events + const MessageTypes = { + IFRAME_LOADED: "iframe-loaded", + READY: "ready", + ERROR: "error", + BUDIBASE: "type", + KEYDOWN: "keydown" + } // Construct iframe template $: template = iframeTemplate.replace( @@ -80,46 +90,44 @@ // Refresh the preview when required $: refreshContent(strippedJson) - onMount(() => { - // Initialise the app when mounted - iframe.contentWindow.addEventListener( - "ready", - () => { + function receiveMessage(message) { + const handlers = { + [MessageTypes.READY]: () => { + // Initialise the app when mounted // Display preview immediately if the intelligent loading feature // is not supported + if (!loading) return + if (!$store.clientFeatures.intelligentLoading) { loading = false } refreshContent(strippedJson) }, - { once: true } - ) - - // Catch any app errors - iframe.contentWindow.addEventListener( - "error", - event => { + [MessageTypes.ERROR]: event => { + // Catch any app errors loading = false - error = event.detail || "An unknown error occurred" + error = event.error || "An unknown error occurred" }, - { once: true } - ) + [MessageTypes.KEYDOWN]: handleKeydownEvent + } - // Add listener for events sent by client library in preview - iframe.contentWindow.addEventListener("bb-event", handleBudibaseEvent) - iframe.contentWindow.addEventListener("keydown", handleKeydownEvent) + const messageHandler = handlers[message.data.type] || handleBudibaseEvent + messageHandler(message) + } + + onMount(() => { + window.addEventListener("message", receiveMessage) }) // Remove all iframe event listeners on component destroy onDestroy(() => { if (iframe.contentWindow) { - iframe.contentWindow.removeEventListener("bb-event", handleBudibaseEvent) - iframe.contentWindow.removeEventListener("keydown", handleKeydownEvent) + window.removeEventListener("message", receiveMessage) // } }) const handleBudibaseEvent = event => { - const { type, data } = event.detail + const { type, data } = event.data if (type === "select-component" && data.id) { store.actions.components.select({ _id: data.id }) } else if (type === "update-prop") { @@ -151,13 +159,14 @@ store.actions.components.paste(destination, data.mode) } } else { - console.warning(`Client sent unknown event type: ${type}`) + console.warn(`Client sent unknown event type: ${type}`) } } const handleKeydownEvent = event => { + const { key } = event.data if ( - (event.key === "Delete" || event.key === "Backspace") && + (key === "Delete" || key === "Backspace") && selectedComponentId && ["input", "textarea"].indexOf( iframe.contentWindow.document.activeElement?.tagName.toLowerCase() diff --git a/packages/builder/src/components/design/AppPreview/iframeTemplate.js b/packages/builder/src/components/design/AppPreview/iframeTemplate.js index 58557273f2..89fa63012d 100644 --- a/packages/builder/src/components/design/AppPreview/iframeTemplate.js +++ b/packages/builder/src/components/design/AppPreview/iframeTemplate.js @@ -54,7 +54,7 @@ export default ` if (!parsed) { return } - + // Extract data from message const { selectedComponentId, @@ -84,17 +84,20 @@ export default ` if (window.loadBudibase) { window.loadBudibase() document.documentElement.classList.add("loaded") - window.dispatchEvent(new Event("iframe-loaded")) + window.parent.postMessage({ type: "iframe-loaded" }) } else { throw "The client library couldn't be loaded" } } catch (error) { - window.dispatchEvent(new CustomEvent("error", { detail: error })) + window.parent.postMessage({ type: "error", error }) } } window.addEventListener("message", receiveMessage) - window.dispatchEvent(new Event("ready")) + window.addEventListener("keydown", evt => { + window.parent.postMessage({ type: "keydown", key: event.key }) + }) + window.parent.postMessage({ type: "ready" }) diff --git a/packages/client/src/components/overlay/PeekScreenDisplay.svelte b/packages/client/src/components/overlay/PeekScreenDisplay.svelte index 0af1ba499e..51ff4412dc 100644 --- a/packages/client/src/components/overlay/PeekScreenDisplay.svelte +++ b/packages/client/src/components/overlay/PeekScreenDisplay.svelte @@ -8,6 +8,12 @@ import { Modal, ModalContent, ActionButton } from "@budibase/bbui" import { onDestroy } from "svelte" + const MessageTypes = { + NOTIFICATION: "notification", + CLOSE_SCREEN_MODAL: "close-screen-modal", + INVALIDATE_DATASOURCE: "invalidate-datasource", + } + let iframe let listenersAttached = false @@ -21,32 +27,33 @@ notificationStore.actions.send(message, type, icon) } + function receiveMessage(message) { + const handlers = { + [MessageTypes.NOTIFICATION]: () => { + proxyNotification(message.data) + }, + [MessageTypes.CLOSE_SCREEN_MODAL]: peekStore.actions.hidePeek, + [MessageTypes.INVALIDATE_DATASOURCE]: () => { + invalidateDataSource(message.data) + }, + } + + const messageHandler = handlers[message.data.type] + if (messageHandler) { + messageHandler(message) + } else { + console.warning("Unknown event type", message?.data?.type) + } + } + const attachListeners = () => { // Mirror datasource invalidation to keep the parent window up to date - iframe.contentWindow.addEventListener( - "invalidate-datasource", - invalidateDataSource - ) - // Listen for a close event to close the screen peek - iframe.contentWindow.addEventListener( - "close-screen-modal", - peekStore.actions.hidePeek - ) - // Proxy notifications back to the parent window instead of iframe - iframe.contentWindow.addEventListener("notification", proxyNotification) + window.addEventListener("message", receiveMessage) } const handleCancel = () => { peekStore.actions.hidePeek() - iframe.contentWindow.removeEventListener( - "invalidate-datasource", - invalidateDataSource - ) - iframe.contentWindow.removeEventListener( - "close-screen-modal", - peekStore.actions.hidePeek - ) - iframe.contentWindow.removeEventListener("notification", proxyNotification) + window.removeEventListener("message", receiveMessage) } const handleFullscreen = () => { diff --git a/packages/client/src/stores/builder.js b/packages/client/src/stores/builder.js index 2740a4551c..baea5cb831 100644 --- a/packages/client/src/stores/builder.js +++ b/packages/client/src/stores/builder.js @@ -4,11 +4,7 @@ import { findComponentById, findComponentPathById } from "../utils/components" import { pingEndUser } from "../api" const dispatchEvent = (type, data = {}) => { - window.dispatchEvent( - new CustomEvent("bb-event", { - detail: { type, data }, - }) - ) + window.parent.postMessage({ type, data }) } const createBuilderStore = () => { diff --git a/packages/client/src/stores/notification.js b/packages/client/src/stores/notification.js index e3af9aacd1..97193b2092 100644 --- a/packages/client/src/stores/notification.js +++ b/packages/client/src/stores/notification.js @@ -26,11 +26,19 @@ const createNotificationStore = () => { // If peeking, pass notifications back to parent window if (get(routeStore).queryParams?.peek) { - window.dispatchEvent( - new CustomEvent("notification", { - detail: { message, type, icon }, - }) - ) + window.parent.postMessage({ + type: "notification", + detail: { + message, + type, + icon, + }, + }) + // window.dispatchEvent( + // new CustomEvent("notification", { + // detail: { message, type, icon }, + // }) + // ) return }