diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js
index 58d803aa03..7903210f53 100644
--- a/packages/builder/src/builderStore/store/frontend.js
+++ b/packages/builder/src/builderStore/store/frontend.js
@@ -92,11 +92,41 @@ export const getFrontendStore = () => {
// Allow errors to propagate.
let components = await API.fetchComponentLibDefinitions(application.appId)
+ // Extend definitions with custom components
+ components["test"] = {
+ component: "test",
+ name: "Super cool component",
+ icon: "Text",
+ description: "A custom component",
+ showSettingsBar: false,
+ hasChildren: true,
+ settings: [
+ {
+ type: "text",
+ key: "text",
+ label: "Text",
+ },
+ ],
+ context: {
+ type: "static",
+ values: [
+ {
+ label: "Text prop",
+ key: "text",
+ },
+ ],
+ },
+ }
+
+ // Filter out custom component keys so we can flag them
+ let customComponents = ["test"]
+
// Reset store state
store.update(state => ({
...state,
libraries: application.componentLibraries,
components,
+ customComponents,
clientFeatures: {
...INITIAL_FRONTEND_STATE.clientFeatures,
...components.features,
@@ -397,9 +427,6 @@ export const getFrontendStore = () => {
if (!componentName) {
return null
}
- if (!componentName.startsWith("@budibase")) {
- componentName = `@budibase/standard-components/${componentName}`
- }
return get(store).components[componentName]
},
createInstance: (componentName, presetProps) => {
diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/iframeTemplate.js b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/iframeTemplate.js
index 1c789d858e..64a05c7246 100644
--- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/iframeTemplate.js
+++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/iframeTemplate.js
@@ -37,6 +37,9 @@ export default `
}
+
-{#if custom}
-
-
-
-{/if}
-
{#if dataLoaded}
import { getContext, setContext, onMount, onDestroy } from "svelte"
import { writable, get } from "svelte/store"
- import * as AppComponents from "components/app"
- import Router from "./Router.svelte"
import {
enrichProps,
propsAreSame,
@@ -180,7 +178,7 @@
// Pull definition and constructor
const component = instance._component
- constructor = getComponentConstructor(component)
+ constructor = componentStore.actions.getComponentConstructor(component)
definition = componentStore.actions.getComponentDefinition(component)
if (!definition) {
return
@@ -237,16 +235,6 @@
})
}
- // Gets the component constructor for the specified component
- const getComponentConstructor = component => {
- const split = component?.split("/")
- const name = split?.[split.length - 1]
- if (name === "screenslot" && !insideScreenslot) {
- return Router
- }
- return AppComponents[name]
- }
-
const getSettingsDefinitionMap = settingsDefinition => {
let map = {}
settingsDefinition?.forEach(setting => {
diff --git a/packages/client/src/index.js b/packages/client/src/index.js
index c88afe59ba..32b242bc69 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 { componentStore, builderStore, appStore, devToolsStore } from "./stores"
import loadSpectrumIcons from "@budibase/bbui/spectrum-icons-rollup.js"
import { get } from "svelte/store"
@@ -38,6 +38,13 @@ const loadBudibase = () => {
const enableDevTools = !get(builderStore).inBuilder && get(appStore).isDevApp
devToolsStore.actions.setEnabled(enableDevTools)
+ // Register any custom components
+ if (window["##BUDIBASE_CUSTOM_COMPONENTS##"]) {
+ window["##BUDIBASE_CUSTOM_COMPONENTS##"].forEach(component => {
+ componentStore.actions.registerCustomComponent(component)
+ })
+ }
+
// Create app if one hasn't been created yet
if (!app) {
app = new ClientApp({
diff --git a/packages/client/src/stores/components.js b/packages/client/src/stores/components.js
index a7426113e2..2500ea2a41 100644
--- a/packages/client/src/stores/components.js
+++ b/packages/client/src/stores/components.js
@@ -4,6 +4,10 @@ import { findComponentById, findComponentPathById } from "../utils/components"
import { devToolsStore } from "./devTools"
import { screenStore } from "./screens"
import { builderStore } from "./builder"
+import Router from "../components/Router.svelte"
+import * as AppComponents from "../components/app/index.js"
+
+const budibasePrefix = "@budibase/standard-components/"
const createComponentStore = () => {
const store = writable({})
@@ -34,6 +38,7 @@ const createComponentStore = () => {
findComponentPathById(asset?.props, selectedComponentId) || []
return {
+ customComponentManifest: $store.customComponentManifest,
selectedComponentInstance: $store[selectedComponentId],
selectedComponent: component,
selectedComponentDefinition: definition,
@@ -68,9 +73,60 @@ const createComponentStore = () => {
}
const getComponentDefinition = type => {
- const prefix = "@budibase/standard-components/"
- type = type?.replace(prefix, "")
- return type ? Manifest[type] : null
+ if (!type) {
+ return null
+ }
+
+ // Screenslot is an edge case
+ if (type === "screenslot") {
+ type = `${budibasePrefix}${type}`
+ }
+
+ // Handle built-in components
+ if (type.startsWith(budibasePrefix)) {
+ type = type.replace(budibasePrefix, "")
+ return type ? Manifest[type] : null
+ }
+
+ // Handle custom components
+ const { customComponentManifest } = get(store)
+ return customComponentManifest?.[type]?.schema?.schema
+ }
+
+ const getComponentConstructor = type => {
+ if (!type) {
+ return null
+ }
+ if (type === "screenslot") {
+ return Router
+ }
+
+ // Handle budibase components
+ if (type.startsWith(budibasePrefix)) {
+ const split = type.split("/")
+ const name = split[split.length - 1]
+ return AppComponents[name]
+ }
+
+ // Handle custom components
+ const { customComponentManifest } = get(store)
+ return customComponentManifest?.[type]?.Component
+ }
+
+ const registerCustomComponent = ({ Component, schema }) => {
+ if (!Component || !schema?.schema?.name) {
+ return
+ }
+ store.update(state => {
+ if (!state.customComponentManifest) {
+ state.customComponentManifest = {}
+ }
+ state.customComponentManifest[schema.schema.name] = {
+ schema,
+ Component,
+ }
+ return state
+ })
}
return {
@@ -81,6 +137,8 @@ const createComponentStore = () => {
isComponentRegistered,
getComponentById,
getComponentDefinition,
+ getComponentConstructor,
+ registerCustomComponent,
},
}
}