Transparently hot reload app preview when inserting a custom component bundle and reload relevant components
This commit is contained in:
parent
5d554df853
commit
3a07002310
|
@ -41,23 +41,10 @@
|
|||
}
|
||||
|
||||
// Construct iframe template
|
||||
$: pluginLinks = generatePluginLinks($store.usedPlugins)
|
||||
$: template = iframeTemplate
|
||||
.replace(/\{\{ CLIENT_LIB_PATH }}/, $store.clientLibPath)
|
||||
.replace(/\{\{ PLUGINS }}/, pluginLinks)
|
||||
|
||||
const generatePluginLinks = plugins => {
|
||||
if (!plugins?.length) {
|
||||
return ""
|
||||
}
|
||||
return plugins
|
||||
.map(plugin => {
|
||||
// Split up like this as otherwise parsing fails because the script
|
||||
// tags confuse svelte
|
||||
return `<` + `script src="/plugins/${plugin.jsUrl}"></` + `script>`
|
||||
})
|
||||
.join("")
|
||||
}
|
||||
$: template = iframeTemplate.replace(
|
||||
/\{\{ CLIENT_LIB_PATH }}/,
|
||||
$store.clientLibPath
|
||||
)
|
||||
|
||||
const placeholderScreen = new Screen()
|
||||
.name("Screen Placeholder")
|
||||
|
|
|
@ -37,7 +37,6 @@ export default `
|
|||
}
|
||||
</style>
|
||||
<script src='{{ CLIENT_LIB_PATH }}'></script>
|
||||
{{ PLUGINS }}
|
||||
<script>
|
||||
function receiveMessage(event) {
|
||||
if (!event.data) {
|
||||
|
@ -67,7 +66,8 @@ export default `
|
|||
customTheme,
|
||||
previewDevice,
|
||||
navigation,
|
||||
hiddenComponentIds
|
||||
hiddenComponentIds,
|
||||
usedPlugins,
|
||||
} = parsed
|
||||
|
||||
// Set some flags so the app knows we're in the builder
|
||||
|
@ -82,6 +82,7 @@ export default `
|
|||
window["##BUDIBASE_PREVIEW_DEVICE##"] = previewDevice
|
||||
window["##BUDIBASE_PREVIEW_NAVIGATION##"] = navigation
|
||||
window["##BUDIBASE_HIDDEN_COMPONENT_IDS##"] = hiddenComponentIds
|
||||
window["##BUDIBASE_USED_PLUGINS##"] = usedPlugins
|
||||
|
||||
// Initialise app
|
||||
try {
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
import { API } from "api"
|
||||
import { writable } from "svelte/store"
|
||||
import { redirect } from "@roxi/routify"
|
||||
import { onMount } from "svelte"
|
||||
|
||||
// Only admins allowed here
|
||||
$: {
|
||||
|
@ -33,11 +34,11 @@
|
|||
})
|
||||
let loading = false
|
||||
|
||||
async function uploadLogo(file) {
|
||||
async function uploadLogo() {
|
||||
try {
|
||||
let data = new FormData()
|
||||
data.append("file", file)
|
||||
await API.uploadLogo(data)
|
||||
data.append("file", $values.logo)
|
||||
await API.uploadPlugin(data)
|
||||
} catch (error) {
|
||||
notifications.error("Error uploading logo")
|
||||
}
|
||||
|
@ -71,6 +72,11 @@
|
|||
}
|
||||
loading = false
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
const plugins = await API.getPlugins()
|
||||
console.log(plugins)
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if $auth.isAdmin}
|
||||
|
@ -106,6 +112,7 @@
|
|||
}
|
||||
}}
|
||||
/>
|
||||
<button on:click={uploadLogo}>Upload</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -87,6 +87,14 @@
|
|||
})
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
{#if $builderStore.usedPlugins?.length}
|
||||
{#each $builderStore.usedPlugins as plugin}
|
||||
<script src={`/plugins/${plugin.jsUrl}`}></script>
|
||||
{/each}
|
||||
{/if}
|
||||
</svelte:head>
|
||||
|
||||
{#if dataLoaded}
|
||||
<div
|
||||
id="spectrum-root"
|
||||
|
|
|
@ -163,14 +163,14 @@
|
|||
missingRequiredSettings,
|
||||
})
|
||||
|
||||
const initialise = instance => {
|
||||
const initialise = (instance, force = false) => {
|
||||
if (instance == null) {
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure we're processing a new instance
|
||||
const instanceKey = Helpers.hashString(JSON.stringify(instance))
|
||||
if (instanceKey === lastInstanceKey) {
|
||||
if (instanceKey === lastInstanceKey && !force) {
|
||||
return
|
||||
} else {
|
||||
lastInstanceKey = instanceKey
|
||||
|
@ -407,9 +407,11 @@
|
|||
!componentStore.actions.isComponentRegistered(id)
|
||||
) {
|
||||
componentStore.actions.registerInstance(id, {
|
||||
component: instance._component,
|
||||
getSettings: () => cachedSettings,
|
||||
getRawSettings: () => ({ ...staticSettings, ...dynamicSettings }),
|
||||
getDataContext: () => get(context),
|
||||
reload: () => initialise(instance, true),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -19,7 +19,10 @@
|
|||
label="Active screen"
|
||||
value={$screenStore.activeScreen?.routing.route}
|
||||
/>
|
||||
<DevToolsStat label="Components" value={$componentStore.mountedComponents} />
|
||||
<DevToolsStat
|
||||
label="Components"
|
||||
value={$componentStore.mountedComponentCount}
|
||||
/>
|
||||
<DevToolsStat label="User" value={$authStore.email} />
|
||||
<DevToolsStat label="Role" value={$authStore.roleId} />
|
||||
</Layout>
|
||||
|
|
|
@ -27,6 +27,7 @@ const loadBudibase = () => {
|
|||
previewDevice: window["##BUDIBASE_PREVIEW_DEVICE##"],
|
||||
navigation: window["##BUDIBASE_PREVIEW_NAVIGATION##"],
|
||||
hiddenComponentIds: window["##BUDIBASE_HIDDEN_COMPONENT_IDS##"],
|
||||
usedPlugins: window["##BUDIBASE_USED_PLUGINS##"],
|
||||
})
|
||||
|
||||
// Set app ID - this window flag is set by both the preview and the real
|
||||
|
@ -39,6 +40,11 @@ const loadBudibase = () => {
|
|||
devToolsStore.actions.setEnabled(enableDevTools)
|
||||
|
||||
// Register any custom components
|
||||
window.registerCustomComponent = plugin => {
|
||||
componentStore.actions.registerCustomComponent(plugin)
|
||||
console.log("registered!")
|
||||
loadBudibase()
|
||||
}
|
||||
if (window["##BUDIBASE_CUSTOM_COMPONENTS##"]) {
|
||||
window["##BUDIBASE_CUSTOM_COMPONENTS##"].forEach(component => {
|
||||
componentStore.actions.registerCustomComponent(component)
|
||||
|
|
|
@ -10,7 +10,11 @@ import * as AppComponents from "../components/app/index.js"
|
|||
const budibasePrefix = "@budibase/standard-components/"
|
||||
|
||||
const createComponentStore = () => {
|
||||
const store = writable({})
|
||||
const store = writable({
|
||||
customComponentManifest: {},
|
||||
componentsAwaitingConstructors: {},
|
||||
mountedComponents: {},
|
||||
})
|
||||
|
||||
const derivedStore = derived(
|
||||
[store, builderStore, devToolsStore, screenStore],
|
||||
|
@ -29,9 +33,7 @@ const createComponentStore = () => {
|
|||
asset = $screenState.activeScreen
|
||||
}
|
||||
const component = findComponentById(asset?.props, selectedComponentId)
|
||||
const prefix = "@budibase/standard-components/"
|
||||
const type = component?._component?.replace(prefix, "")
|
||||
const definition = type ? Manifest[type] : null
|
||||
const definition = getComponentDefinition(component?._component)
|
||||
|
||||
// Derive the selected component path
|
||||
const path =
|
||||
|
@ -39,32 +41,50 @@ const createComponentStore = () => {
|
|||
|
||||
return {
|
||||
customComponentManifest: $store.customComponentManifest,
|
||||
selectedComponentInstance: $store[selectedComponentId],
|
||||
selectedComponentInstance:
|
||||
$store.mountedComponents[selectedComponentId],
|
||||
selectedComponent: component,
|
||||
selectedComponentDefinition: definition,
|
||||
selectedComponentPath: path?.map(component => component._id),
|
||||
mountedComponents: Object.keys($store).length,
|
||||
mountedComponentCount: Object.keys($store.mountedComponents).length,
|
||||
currentAsset: asset,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const registerInstance = (id, instance) => {
|
||||
store.update(state => ({
|
||||
...state,
|
||||
[id]: instance,
|
||||
}))
|
||||
store.update(state => {
|
||||
// If this is a custom component and does not have an implementation yet,
|
||||
// store so we can reload this component later
|
||||
const component = instance.component
|
||||
let cac = state.componentsAwaitingConstructors
|
||||
if (!getComponentConstructor(component)) {
|
||||
if (!cac[component]) {
|
||||
cac[component] = []
|
||||
}
|
||||
cac[component].push(id)
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
componentsAwaitingConstructors: cac,
|
||||
mountedComponents: {
|
||||
...state.mountedComponents,
|
||||
[id]: instance,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const unregisterInstance = id => {
|
||||
store.update(state => {
|
||||
delete state[id]
|
||||
delete state.mountedComponents[id]
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
||||
const isComponentRegistered = id => {
|
||||
return get(store)[id] != null
|
||||
return get(store).mountedComponents[id] != null
|
||||
}
|
||||
|
||||
const getComponentById = id => {
|
||||
|
@ -117,17 +137,32 @@ const createComponentStore = () => {
|
|||
if (!Component || !schema?.schema?.name) {
|
||||
return
|
||||
}
|
||||
const componentName = `plugin/${schema.schema.name}/1.0.0`
|
||||
store.update(state => {
|
||||
if (!state.customComponentManifest) {
|
||||
state.customComponentManifest = {}
|
||||
}
|
||||
const componentName = `plugin/${schema.schema.name}/1.0.0`
|
||||
state.customComponentManifest[componentName] = {
|
||||
schema,
|
||||
Component,
|
||||
}
|
||||
return state
|
||||
})
|
||||
|
||||
// Reload any mounted components which depend on this definition
|
||||
const state = get(store)
|
||||
if (state.componentsAwaitingConstructors[componentName]?.length) {
|
||||
state.componentsAwaitingConstructors[componentName].forEach(id => {
|
||||
const instance = state.mountedComponents[id]
|
||||
if (instance) {
|
||||
instance.reload()
|
||||
}
|
||||
})
|
||||
store.update(state => {
|
||||
delete state.componentsAwaitingConstructors[componentName]
|
||||
return state
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
Loading…
Reference in New Issue