1
0
Fork 0
mirror of synced 2024-06-29 11:31:06 +12:00
budibase/packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte

293 lines
7.7 KiB
Svelte
Raw Normal View History

2019-08-20 08:18:23 +12:00
<script>
import { get } from "svelte/store"
import { onMount, onDestroy } from "svelte"
2021-06-16 06:36:56 +12:00
import { store, currentAsset } from "builderStore"
2020-05-07 21:53:34 +12:00
import iframeTemplate from "./iframeTemplate"
2020-11-24 00:29:24 +13:00
import { Screen } from "builderStore/store/screenTemplates/utils/Screen"
import { FrontendTypes } from "constants"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import {
ProgressCircle,
Layout,
Heading,
Body,
notifications,
} from "@budibase/bbui"
import ErrorSVG from "assets/error.svg?raw"
import { findComponent, findComponentPath } from "builderStore/componentUtils"
2019-09-03 21:42:19 +12:00
2020-02-12 05:36:16 +13:00
let iframe
let layout
let screen
let confirmDeleteDialog
let idToDelete
let loading = true
let error
2020-05-26 02:23:56 +12:00
// Create screen slot placeholder for use when a page is selected rather
// than a screen
const screenPlaceholder = new Screen()
.name("Screen Placeholder")
.route("*")
.component("@budibase/standard-components/screenslot")
.instanceName("Content Placeholder")
.json()
2020-06-09 08:13:19 +12:00
2021-11-05 05:22:02 +13:00
// Messages that can be sent from the iframe preview to the builder
// Budibase events are and initalisation events
const MessageTypes = {
READY: "ready",
ERROR: "error",
BUDIBASE: "type",
}
2020-06-09 08:13:19 +12:00
// Construct iframe template
$: template = iframeTemplate.replace(
/\{\{ CLIENT_LIB_PATH }}/,
$store.clientLibPath
)
2020-11-24 00:29:24 +13:00
// Extract data to pass to the iframe
$: {
if ($store.currentFrontEndType === FrontendTypes.LAYOUT) {
layout = $currentAsset
screen = screenPlaceholder
} else {
screen = $currentAsset
2020-12-08 04:49:13 +13:00
layout = $store.layouts.find(layout => layout._id === screen?.layoutId)
}
}
$: selectedComponentId = $store.selectedComponentId ?? ""
2020-11-24 00:29:24 +13:00
$: previewData = {
appId: $store.appId,
layout,
2020-11-24 00:29:24 +13:00
screen,
selectedComponentId,
previewType: $store.currentFrontEndType,
theme: $store.theme,
customTheme: $store.customTheme,
previewDevice: $store.previewDevice,
messagePassing: $store.clientFeatures.messagePassing,
2020-02-12 05:36:16 +13:00
}
$: json = JSON.stringify(previewData)
2020-02-12 05:36:16 +13:00
2020-11-24 00:29:24 +13:00
// Update the iframe with the builder info to render the correct preview
const refreshContent = message => {
2020-11-24 00:29:24 +13:00
if (iframe) {
iframe.contentWindow.postMessage(message)
2020-11-24 00:29:24 +13:00
}
2020-06-09 08:13:19 +12:00
}
2020-06-09 17:22:00 +12:00
// Refresh the preview when required
$: refreshContent(json)
2020-11-24 00:29:24 +13:00
2021-11-05 05:22:02 +13:00
function receiveMessage(message) {
const handlers = {
[MessageTypes.READY]: () => {
// Initialise the app when mounted
2021-11-10 00:15:29 +13:00
if ($store.clientFeatures.messagePassing) {
if (!loading) return
}
2021-11-05 05:22:02 +13:00
// Display preview immediately if the intelligent loading feature
// is not supported
if (!$store.clientFeatures.intelligentLoading) {
loading = false
}
refreshContent(json)
},
2021-11-05 05:22:02 +13:00
[MessageTypes.ERROR]: event => {
// Catch any app errors
loading = false
2021-11-05 05:22:02 +13:00
error = event.error || "An unknown error occurred"
},
2021-11-05 05:22:02 +13:00
}
const messageHandler = handlers[message.data.type] || handleBudibaseEvent
messageHandler(message)
}
2021-11-05 05:22:02 +13:00
onMount(() => {
2021-11-11 00:12:33 +13:00
window.addEventListener("message", receiveMessage)
if (!$store.clientFeatures.messagePassing) {
2021-11-10 00:15:29 +13:00
// 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 }
)
2021-11-10 00:15:29 +13:00
// Add listener for events sent by client library in preview
iframe.contentWindow.addEventListener("bb-event", handleBudibaseEvent)
}
2020-11-24 00:29:24 +13:00
})
// Remove all iframe event listeners on component destroy
onDestroy(() => {
if (iframe.contentWindow) {
2021-11-11 00:12:33 +13:00
window.removeEventListener("message", receiveMessage)
if (!$store.clientFeatures.messagePassing) {
2021-11-10 00:15:29 +13:00
// Legacy - remove in later versions of BB
iframe.contentWindow.removeEventListener(
"bb-event",
handleBudibaseEvent
)
2021-11-10 00:15:29 +13:00
}
}
2020-11-24 00:29:24 +13:00
})
const handleBudibaseEvent = event => {
2021-11-10 00:15:29 +13:00
const { type, data } = event.data || event.detail
if (!type) {
return
}
if (type === "select-component" && data.id) {
store.actions.components.select({ _id: data.id })
} else if (type === "update-prop") {
store.actions.components.updateProp(data.prop, data.value)
} else if (type === "delete-component" && data.id) {
confirmDeleteComponent(data.id)
} else if (type === "preview-loaded") {
// Wait for this event to show the client library if intelligent
// loading is supported
loading = false
} else if (type === "move-component") {
const { componentId, destinationComponentId } = data
const rootComponent = get(currentAsset).props
// Get source and destination components
const source = findComponent(rootComponent, componentId)
const destination = findComponent(rootComponent, destinationComponentId)
// Stop if the target is a child of source
const path = findComponentPath(source, destinationComponentId)
const ids = path.map(component => component._id)
if (ids.includes(data.destinationComponentId)) {
return
}
// Cut and paste the component to the new destination
if (source && destination) {
store.actions.components.copy(source, true)
store.actions.components.paste(destination, data.mode)
}
} else {
2021-11-05 05:22:02 +13:00
console.warn(`Client sent unknown event type: ${type}`)
}
}
const confirmDeleteComponent = componentId => {
2021-07-27 09:48:59 +12:00
idToDelete = componentId
confirmDeleteDialog.show()
}
const deleteComponent = async () => {
try {
await store.actions.components.delete({ _id: idToDelete })
} catch (error) {
notifications.error(error)
}
idToDelete = null
}
const cancelDeleteComponent = () => {
idToDelete = null
}
2020-02-12 05:36:16 +13:00
</script>
<div class="component-container">
{#if loading}
<div class="center">
<ProgressCircle />
</div>
{:else if error}
<div class="center error">
<Layout justifyItems="center" gap="S">
{@html ErrorSVG}
<Heading size="L">App preview failed to load</Heading>
<Body size="S">{error}</Body>
</Layout>
</div>
{/if}
2020-12-08 23:16:01 +13:00
<iframe
title="componentPreview"
bind:this={iframe}
srcdoc={template}
class:hidden={loading || error}
class:tablet={$store.previewDevice === "tablet"}
class:mobile={$store.previewDevice === "mobile"}
/>
2019-08-20 08:18:23 +12:00
</div>
<ConfirmDialog
bind:this={confirmDeleteDialog}
title="Confirm Deletion"
body={`Are you sure you want to delete this component?`}
okText="Delete component"
onOk={deleteComponent}
onCancel={cancelDeleteComponent}
/>
2019-08-20 08:18:23 +12:00
<style>
2020-02-12 05:36:16 +13:00
.component-container {
grid-row-start: middle;
grid-column-start: middle;
display: grid;
place-items: center;
2020-02-12 05:36:16 +13:00
position: relative;
overflow: hidden;
margin: auto;
height: 100%;
2020-02-12 05:36:16 +13:00
}
.component-container iframe {
border: 0;
left: 0;
top: 0;
width: 100%;
background-color: transparent;
2020-02-12 05:36:16 +13:00
}
.center {
position: absolute;
width: 100%;
height: 100%;
display: grid;
place-items: center;
z-index: 1;
}
.hidden {
opacity: 0;
}
.error :global(svg) {
fill: var(--spectrum-global-color-gray-500);
width: 80px;
height: 80px;
}
.error :global(h1),
.error :global(p) {
color: var(--spectrum-global-color-gray-800);
}
.error :global(p) {
font-style: italic;
margin-top: -0.5em;
}
.error :global(h1) {
font-weight: 400;
margin: 0;
}
iframe {
width: 100%;
height: 100%;
}
2020-02-19 04:53:22 +13:00
</style>