Refactor app preview event sending to support async callbacks in client library
This commit is contained in:
parent
986141b8b1
commit
8d644c5a10
|
@ -103,7 +103,7 @@
|
|||
)
|
||||
|
||||
// Register handler to send custom to the preview
|
||||
$: store.actions.preview.registerEventHandler((name, payload) => {
|
||||
$: sendPreviewEvent = (name, payload) => {
|
||||
iframe?.contentWindow.postMessage(
|
||||
JSON.stringify({
|
||||
name,
|
||||
|
@ -112,16 +112,38 @@
|
|||
runtimeEvent: true,
|
||||
})
|
||||
)
|
||||
})
|
||||
}
|
||||
$: store.actions.preview.registerEventHandler(sendPreviewEvent)
|
||||
|
||||
// Update the iframe with the builder info to render the correct preview
|
||||
const refreshContent = message => {
|
||||
iframe?.contentWindow.postMessage(message)
|
||||
}
|
||||
|
||||
const receiveMessage = message => {
|
||||
const handlers = {
|
||||
[MessageTypes.READY]: () => {
|
||||
const receiveMessage = async message => {
|
||||
if (!message?.data?.type) {
|
||||
return
|
||||
}
|
||||
|
||||
// Await the event handler
|
||||
try {
|
||||
await handleBudibaseEvent(message)
|
||||
} catch (error) {
|
||||
notifications.error(error || "Error handling event from app preview")
|
||||
}
|
||||
|
||||
// Reply that the event has been completed
|
||||
if (message.data?.id) {
|
||||
sendPreviewEvent("event-completed", message.data?.id)
|
||||
}
|
||||
}
|
||||
|
||||
const handleBudibaseEvent = async event => {
|
||||
const { type, data } = event.data || event.detail
|
||||
if (!type) {
|
||||
return
|
||||
}
|
||||
if (type === MessageTypes.READY) {
|
||||
// Initialise the app when mounted
|
||||
if ($store.clientFeatures.messagePassing) {
|
||||
if (!loading) return
|
||||
|
@ -133,26 +155,11 @@
|
|||
loading = false
|
||||
}
|
||||
refreshContent(json)
|
||||
},
|
||||
[MessageTypes.ERROR]: event => {
|
||||
} else if (type === MessageTypes.ERROR) {
|
||||
// Catch any app errors
|
||||
loading = false
|
||||
error = event.error || "An unknown error occurred"
|
||||
},
|
||||
}
|
||||
|
||||
const messageHandler = handlers[message.data.type] || handleBudibaseEvent
|
||||
messageHandler(message)
|
||||
}
|
||||
|
||||
const handleBudibaseEvent = async event => {
|
||||
const { type, data } = event.data || event.detail
|
||||
if (!type) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
if (type === "select-component" && data.id) {
|
||||
} else if (type === "select-component" && data.id) {
|
||||
$store.selectedComponentId = data.id
|
||||
if (!$isActive("./components")) {
|
||||
$goto("./components")
|
||||
|
@ -225,9 +232,6 @@
|
|||
} else {
|
||||
console.warn(`Client sent unknown event type: ${type}`)
|
||||
}
|
||||
} catch (error) {
|
||||
notifications.error(error || "Error handling event from app preview")
|
||||
}
|
||||
}
|
||||
|
||||
const confirmDeleteComponent = componentId => {
|
||||
|
@ -259,42 +263,10 @@
|
|||
|
||||
onMount(() => {
|
||||
window.addEventListener("message", receiveMessage)
|
||||
if (!$store.clientFeatures.messagePassing) {
|
||||
// Legacy - remove in later versions of BB
|
||||
iframe.contentWindow.addEventListener(
|
||||
"ready",
|
||||
() => {
|
||||
receiveMessage({ data: { type: MessageTypes.READY } })
|
||||
},
|
||||
{ once: true }
|
||||
)
|
||||
iframe.contentWindow.addEventListener(
|
||||
"error",
|
||||
event => {
|
||||
receiveMessage({
|
||||
data: { type: MessageTypes.ERROR, error: event.detail },
|
||||
})
|
||||
},
|
||||
{ once: true }
|
||||
)
|
||||
// Add listener for events sent by client library in preview
|
||||
iframe.contentWindow.addEventListener("bb-event", handleBudibaseEvent)
|
||||
}
|
||||
})
|
||||
|
||||
// Remove all iframe event listeners on component destroy
|
||||
onDestroy(() => {
|
||||
window.removeEventListener("message", receiveMessage)
|
||||
|
||||
if (iframe.contentWindow) {
|
||||
if (!$store.clientFeatures.messagePassing) {
|
||||
// Legacy - remove in later versions of BB
|
||||
iframe.contentWindow.removeEventListener(
|
||||
"bb-event",
|
||||
handleBudibaseEvent
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
@ -22,6 +22,9 @@
|
|||
$: target = $dndStore.target
|
||||
$: drop = $dndStore.drop
|
||||
|
||||
// Local flag for whether we are awaiting an async drop event
|
||||
let dropping = false
|
||||
|
||||
const insideGrid = e => {
|
||||
return e.target
|
||||
?.closest?.(".component")
|
||||
|
@ -48,6 +51,10 @@
|
|||
|
||||
// Callback when drag stops (whether dropped or not)
|
||||
const stopDragging = () => {
|
||||
if (dropping) {
|
||||
return
|
||||
}
|
||||
|
||||
// Reset listener
|
||||
if (source?.id) {
|
||||
const component = document.getElementsByClassName(source?.id)[0]
|
||||
|
@ -57,10 +64,8 @@
|
|||
}
|
||||
|
||||
// Reset state
|
||||
if (!$dndStore.dropped) {
|
||||
dndStore.actions.reset()
|
||||
}
|
||||
}
|
||||
|
||||
// Callback when initially starting a drag on a draggable component
|
||||
const onDragStart = e => {
|
||||
|
@ -253,18 +258,21 @@
|
|||
}
|
||||
|
||||
// Callback when dropping a drag on top of some component
|
||||
const onDrop = () => {
|
||||
const onDrop = async () => {
|
||||
if (!source || !drop?.parent || drop?.index == null) {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if we're adding a new component rather than moving one
|
||||
if (source.newComponentType) {
|
||||
builderStore.actions.dropNewComponent(
|
||||
dropping = true
|
||||
await builderStore.actions.dropNewComponent(
|
||||
source.newComponentType,
|
||||
drop.parent,
|
||||
drop.index
|
||||
)
|
||||
dropping = false
|
||||
stopDragging()
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -301,12 +309,14 @@
|
|||
}
|
||||
|
||||
if (legacyDropTarget && legacyDropMode) {
|
||||
dndStore.actions.markDropped()
|
||||
builderStore.actions.moveComponent(
|
||||
dropping = true
|
||||
await builderStore.actions.moveComponent(
|
||||
source.id,
|
||||
legacyDropTarget,
|
||||
legacyDropMode
|
||||
)
|
||||
dropping = false
|
||||
stopDragging()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
componentStore,
|
||||
environmentStore,
|
||||
dndStore,
|
||||
eventStore,
|
||||
} from "./stores"
|
||||
import loadSpectrumIcons from "@budibase/bbui/spectrum-icons-rollup.js"
|
||||
import { get } from "svelte/store"
|
||||
|
@ -42,9 +43,9 @@ const loadBudibase = async () => {
|
|||
})
|
||||
|
||||
// Reset DND state if we completed a successful drop
|
||||
if (get(dndStore).dropped) {
|
||||
dndStore.actions.reset()
|
||||
}
|
||||
// if (get(dndStore).dropped) {
|
||||
// dndStore.actions.reset()
|
||||
// }
|
||||
|
||||
// Set app ID - this window flag is set by both the preview and the real
|
||||
// server rendered app HTML
|
||||
|
@ -59,15 +60,17 @@ const loadBudibase = async () => {
|
|||
devToolsStore.actions.setEnabled(enableDevTools)
|
||||
|
||||
// Register handler for runtime events from the builder
|
||||
window.handleBuilderRuntimeEvent = (name, payload) => {
|
||||
window.handleBuilderRuntimeEvent = (type, data) => {
|
||||
if (!window["##BUDIBASE_IN_BUILDER##"]) {
|
||||
return
|
||||
}
|
||||
if (name === "eject-block") {
|
||||
const block = blockStore.actions.getBlock(payload)
|
||||
if (type === "event-completed") {
|
||||
eventStore.actions.resolveEvent(data)
|
||||
} else if (type === "eject-block") {
|
||||
const block = blockStore.actions.getBlock(data)
|
||||
block?.eject()
|
||||
} else if (name === "dragging-new-component") {
|
||||
const { dragging, component } = payload
|
||||
} else if (type === "dragging-new-component") {
|
||||
const { dragging, component } = data
|
||||
if (dragging) {
|
||||
const definition =
|
||||
componentStore.actions.getComponentDefinition(component)
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
import { writable, get } from "svelte/store"
|
||||
import { API } from "api"
|
||||
import { devToolsStore } from "./devTools.js"
|
||||
|
||||
const dispatchEvent = (type, data = {}) => {
|
||||
window.parent.postMessage({ type, data })
|
||||
}
|
||||
import { eventStore } from "./events.js"
|
||||
|
||||
const createBuilderStore = () => {
|
||||
const initialState = {
|
||||
|
@ -19,6 +16,7 @@ const createBuilderStore = () => {
|
|||
navigation: null,
|
||||
hiddenComponentIds: [],
|
||||
usedPlugins: null,
|
||||
eventResolvers: {},
|
||||
|
||||
// Legacy - allow the builder to specify a layout
|
||||
layout: null,
|
||||
|
@ -35,25 +33,25 @@ const createBuilderStore = () => {
|
|||
selectedComponentId: id,
|
||||
}))
|
||||
devToolsStore.actions.setAllowSelection(false)
|
||||
dispatchEvent("select-component", { id })
|
||||
eventStore.actions.dispatchEvent("select-component", { id })
|
||||
},
|
||||
updateProp: (prop, value) => {
|
||||
dispatchEvent("update-prop", { prop, value })
|
||||
eventStore.actions.dispatchEvent("update-prop", { prop, value })
|
||||
},
|
||||
updateStyles: (styles, id) => {
|
||||
dispatchEvent("update-styles", { styles, id })
|
||||
eventStore.actions.dispatchEvent("update-styles", { styles, id })
|
||||
},
|
||||
keyDown: (key, ctrlKey) => {
|
||||
dispatchEvent("key-down", { key, ctrlKey })
|
||||
eventStore.actions.dispatchEvent("key-down", { key, ctrlKey })
|
||||
},
|
||||
duplicateComponent: id => {
|
||||
dispatchEvent("duplicate-component", { id })
|
||||
eventStore.actions.dispatchEvent("duplicate-component", { id })
|
||||
},
|
||||
deleteComponent: id => {
|
||||
dispatchEvent("delete-component", { id })
|
||||
eventStore.actions.dispatchEvent("delete-component", { id })
|
||||
},
|
||||
notifyLoaded: () => {
|
||||
dispatchEvent("preview-loaded")
|
||||
eventStore.actions.dispatchEvent("preview-loaded")
|
||||
},
|
||||
analyticsPing: async () => {
|
||||
try {
|
||||
|
@ -62,15 +60,15 @@ const createBuilderStore = () => {
|
|||
// Do nothing
|
||||
}
|
||||
},
|
||||
moveComponent: (componentId, destinationComponentId, mode) => {
|
||||
dispatchEvent("move-component", {
|
||||
moveComponent: async (componentId, destinationComponentId, mode) => {
|
||||
await eventStore.actions.dispatchEvent("move-component", {
|
||||
componentId,
|
||||
destinationComponentId,
|
||||
mode,
|
||||
})
|
||||
},
|
||||
dropNewComponent: (component, parent, index) => {
|
||||
dispatchEvent("drop-new-component", {
|
||||
eventStore.actions.dispatchEvent("drop-new-component", {
|
||||
component,
|
||||
parent,
|
||||
index,
|
||||
|
@ -83,16 +81,16 @@ const createBuilderStore = () => {
|
|||
store.update(state => ({ ...state, editMode: enabled }))
|
||||
},
|
||||
clickNav: () => {
|
||||
dispatchEvent("click-nav")
|
||||
eventStore.actions.dispatchEvent("click-nav")
|
||||
},
|
||||
requestAddComponent: () => {
|
||||
dispatchEvent("request-add-component")
|
||||
eventStore.actions.dispatchEvent("request-add-component")
|
||||
},
|
||||
highlightSetting: setting => {
|
||||
dispatchEvent("highlight-setting", { setting })
|
||||
eventStore.actions.dispatchEvent("highlight-setting", { setting })
|
||||
},
|
||||
ejectBlock: (id, definition) => {
|
||||
dispatchEvent("eject-block", { id, definition })
|
||||
eventStore.actions.dispatchEvent("eject-block", { id, definition })
|
||||
},
|
||||
updateUsedPlugin: (name, hash) => {
|
||||
// Check if we used this plugin
|
||||
|
@ -109,7 +107,7 @@ const createBuilderStore = () => {
|
|||
}
|
||||
|
||||
// Notify the builder so we can reload component definitions
|
||||
dispatchEvent("reload-plugin")
|
||||
eventStore.actions.dispatchEvent("reload-plugin")
|
||||
},
|
||||
}
|
||||
return {
|
||||
|
|
|
@ -11,9 +11,6 @@ const createDndStore = () => {
|
|||
|
||||
// Info about where the component would be dropped
|
||||
drop: null,
|
||||
|
||||
// Whether the current drop has been completed successfully
|
||||
dropped: false,
|
||||
}
|
||||
const store = writable(initialState)
|
||||
|
||||
|
@ -63,13 +60,6 @@ const createDndStore = () => {
|
|||
store.set(initialState)
|
||||
}
|
||||
|
||||
const markDropped = () => {
|
||||
store.update(state => {
|
||||
state.dropped = true
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe: store.subscribe,
|
||||
actions: {
|
||||
|
@ -78,7 +68,6 @@ const createDndStore = () => {
|
|||
updateTarget,
|
||||
updateDrop,
|
||||
reset,
|
||||
markDropped,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -91,7 +80,6 @@ export const dndStore = createDndStore()
|
|||
// or components which depend on DND state unless values actually change.
|
||||
export const dndParent = computed(dndStore, x => x.drop?.parent)
|
||||
export const dndIndex = computed(dndStore, x => x.drop?.index)
|
||||
export const dndDropped = computed(dndStore, x => x.dropped)
|
||||
export const dndBounds = computed(dndStore, x => x.source?.bounds)
|
||||
export const dndIsDragging = computed(dndStore, x => !!x.source)
|
||||
export const dndIsNewComponent = computed(
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
import { writable, get } from "svelte/store"
|
||||
|
||||
const createEventStore = () => {
|
||||
const initialState = {
|
||||
eventResolvers: {},
|
||||
}
|
||||
const store = writable(initialState)
|
||||
|
||||
const actions = {
|
||||
dispatchEvent: (type, data) => {
|
||||
const id = Math.random()
|
||||
return new Promise(resolve => {
|
||||
window.parent.postMessage({ type, data, id })
|
||||
store.update(state => {
|
||||
state.eventResolvers[id] = resolve
|
||||
return state
|
||||
})
|
||||
})
|
||||
},
|
||||
resolveEvent: data => {
|
||||
get(store).eventResolvers[data]?.()
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe: store.subscribe,
|
||||
actions,
|
||||
}
|
||||
}
|
||||
|
||||
export const eventStore = createEventStore()
|
|
@ -15,6 +15,7 @@ export { uploadStore } from "./uploads.js"
|
|||
export { rowSelectionStore } from "./rowSelection.js"
|
||||
export { blockStore } from "./blocks.js"
|
||||
export { environmentStore } from "./environment"
|
||||
export { eventStore } from "./events.js"
|
||||
export {
|
||||
dndStore,
|
||||
dndIndex,
|
||||
|
@ -22,7 +23,6 @@ export {
|
|||
dndBounds,
|
||||
dndIsNewComponent,
|
||||
dndIsDragging,
|
||||
dndDropped,
|
||||
} from "./dnd"
|
||||
|
||||
// Context stores are layered and duplicated, so it is not a singleton
|
||||
|
|
|
@ -2,13 +2,7 @@ import { derived } from "svelte/store"
|
|||
import { routeStore } from "./routes"
|
||||
import { builderStore } from "./builder"
|
||||
import { appStore } from "./app"
|
||||
import {
|
||||
dndIndex,
|
||||
dndParent,
|
||||
dndIsNewComponent,
|
||||
dndBounds,
|
||||
dndDropped,
|
||||
} from "./dnd.js"
|
||||
import { dndIndex, dndParent, dndIsNewComponent, dndBounds } from "./dnd.js"
|
||||
import { RoleUtils } from "@budibase/frontend-core"
|
||||
import { findComponentById, findComponentParent } from "../utils/components.js"
|
||||
import { Helpers } from "@budibase/bbui"
|
||||
|
@ -24,7 +18,6 @@ const createScreenStore = () => {
|
|||
dndIndex,
|
||||
dndIsNewComponent,
|
||||
dndBounds,
|
||||
dndDropped,
|
||||
],
|
||||
([
|
||||
$appStore,
|
||||
|
@ -34,7 +27,6 @@ const createScreenStore = () => {
|
|||
$dndIndex,
|
||||
$dndIsNewComponent,
|
||||
$dndBounds,
|
||||
$dndDropped,
|
||||
]) => {
|
||||
let activeLayout, activeScreen
|
||||
let screens
|
||||
|
@ -80,9 +72,6 @@ const createScreenStore = () => {
|
|||
activeScreen.props,
|
||||
selectedComponentId
|
||||
)
|
||||
const selectedComponent = selectedParent?._children?.find(
|
||||
x => x._id === selectedComponentId
|
||||
)
|
||||
|
||||
// Remove selected component from tree if we are moving an existing
|
||||
// component
|
||||
|
@ -92,13 +81,8 @@ const createScreenStore = () => {
|
|||
)
|
||||
}
|
||||
|
||||
// Insert placeholder component if dragging, or artificially insert
|
||||
// the dropped component in the new location if the drop completed
|
||||
let componentToInsert
|
||||
if ($dndDropped && !$dndIsNewComponent) {
|
||||
componentToInsert = selectedComponent
|
||||
} else {
|
||||
componentToInsert = {
|
||||
// Insert placeholder component
|
||||
const componentToInsert = {
|
||||
_component: "@budibase/standard-components/container",
|
||||
_id: DNDPlaceholderID,
|
||||
_styles: {
|
||||
|
@ -110,8 +94,6 @@ const createScreenStore = () => {
|
|||
},
|
||||
static: true,
|
||||
}
|
||||
}
|
||||
if (componentToInsert) {
|
||||
let parent = findComponentById(activeScreen.props, $dndParent)
|
||||
if (parent) {
|
||||
if (!parent._children?.length) {
|
||||
|
@ -121,7 +103,6 @@ const createScreenStore = () => {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assign ranks to screens, preferring higher roles and home screens
|
||||
screens.forEach(screen => {
|
||||
|
|
Loading…
Reference in New Issue