From 0a338181cab8e551258635aa8d8cadeca2affea5 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 10 Aug 2022 15:34:00 +0100 Subject: [PATCH 1/3] Update client library to full handle custom components --- .../client/src/components/ClientApp.svelte | 10 --- .../client/src/components/Component.svelte | 14 +--- packages/client/src/components/Screen.svelte | 17 ++++- packages/client/src/index.js | 9 ++- packages/client/src/stores/components.js | 64 ++++++++++++++++++- 5 files changed, 86 insertions(+), 28 deletions(-) diff --git a/packages/client/src/components/ClientApp.svelte b/packages/client/src/components/ClientApp.svelte index c883dd4206..64b1712b89 100644 --- a/packages/client/src/components/ClientApp.svelte +++ b/packages/client/src/components/ClientApp.svelte @@ -85,18 +85,8 @@ builderStore.actions.notifyLoaded() } }) - - // TODO: remove. this is a test to render the first custom component - console.log(window["##BUDIBASE_CUSTOM_COMPONENTS##"]?.[0]) - const custom = window["##BUDIBASE_CUSTOM_COMPONENTS##"]?.[0]?.Component -{#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/components/Screen.svelte b/packages/client/src/components/Screen.svelte index 3ec8d1ea52..12e6803aea 100644 --- a/packages/client/src/components/Screen.svelte +++ b/packages/client/src/components/Screen.svelte @@ -13,7 +13,22 @@ $: routeStore.actions.setRouteParams(params || {}) // Get the screen definition for the current route - $: screenDefinition = $screenStore.activeScreen?.props + $: screenDefinition = getScreenDefinition($screenStore.activeScreen?.props) + + const getScreenDefinition = definition => { + const test = { + _component: "test", + _id: "asfdasdfefgerdgrfdg", + _styles: { + normal: {}, + }, + text: "This is a text prop!", + } + return { + ...definition, + _children: [...definition._children, test], + } + } onMount(() => { // Mark the router as loaded whenever the screen mounts 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, }, } } From eb1699381c2eb64148bfff65485cf273b2f9af8e Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 10 Aug 2022 15:52:19 +0100 Subject: [PATCH 2/3] Remove forced custom component from client library --- packages/client/src/components/Screen.svelte | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/packages/client/src/components/Screen.svelte b/packages/client/src/components/Screen.svelte index 12e6803aea..3ec8d1ea52 100644 --- a/packages/client/src/components/Screen.svelte +++ b/packages/client/src/components/Screen.svelte @@ -13,22 +13,7 @@ $: routeStore.actions.setRouteParams(params || {}) // Get the screen definition for the current route - $: screenDefinition = getScreenDefinition($screenStore.activeScreen?.props) - - const getScreenDefinition = definition => { - const test = { - _component: "test", - _id: "asfdasdfefgerdgrfdg", - _styles: { - normal: {}, - }, - text: "This is a text prop!", - } - return { - ...definition, - _children: [...definition._children, test], - } - } + $: screenDefinition = $screenStore.activeScreen?.props onMount(() => { // Mark the router as loaded whenever the screen mounts From 737d5e1ef335c7adadc54e5ac88afe9b431f9842 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 10 Aug 2022 16:54:13 +0100 Subject: [PATCH 3/3] Add full PoC of using a custom component inside the builder, with children and bindings --- .../src/builderStore/store/frontend.js | 33 +++++++++++++++++-- .../[screenId]/_components/iframeTemplate.js | 3 ++ .../new/_components/NewComponentPanel.svelte | 21 ++++++++++-- 3 files changed, 51 insertions(+), 6 deletions(-) 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 ` } +