budibase/packages/client/src/stores/components.js

188 lines
5.5 KiB
JavaScript
Raw Normal View History

import { get, writable, derived } from "svelte/store"
import Manifest from "manifest.json"
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 Placeholder from "../components/preview/Placeholder.svelte"
import * as AppComponents from "../components/app/index.js"
const budibasePrefix = "@budibase/standard-components/"
const createComponentStore = () => {
const store = writable({
customComponentManifest: {},
customComponentMap: {},
mountedComponents: {},
})
const derivedStore = derived(
[store, builderStore, devToolsStore, screenStore],
([$store, $builderState, $devToolsState, $screenState]) => {
// Avoid any of this logic if we aren't in the builder preview
if (!$builderState.inBuilder && !$devToolsState.visible) {
return {}
}
// Derive the selected component instance and definition
let asset
const { screen, selectedComponentId } = $builderState
if ($builderState.inBuilder) {
asset = screen
} else {
asset = $screenState.activeScreen
}
const component = findComponentById(asset?.props, selectedComponentId)
const definition = getComponentDefinition(component?._component)
// Derive the selected component path
const selectedPath =
findComponentPathById(asset?.props, selectedComponentId) || []
const dndPath =
findComponentPathById(asset?.props, $builderState.dndParent) || []
return {
customComponentManifest: $store.customComponentManifest,
selectedComponentInstance:
$store.mountedComponents[selectedComponentId],
selectedComponent: component,
selectedComponentDefinition: definition,
selectedComponentPath: selectedPath?.map(component => component._id),
mountedComponentCount: Object.keys($store.mountedComponents).length,
currentAsset: asset,
dndPath: dndPath?.map(component => component._id),
}
}
)
const registerInstance = (id, instance) => {
store.update(state => {
// If this is a custom component, flag it so we can reload this component
// later if required
const component = instance.component
if (component?.startsWith("plugin")) {
if (!state.customComponentMap[component]) {
state.customComponentMap[component] = [id]
} else {
state.customComponentMap[component].push(id)
}
}
// Register to mounted components
state.mountedComponents[id] = instance
return state
})
}
const unregisterInstance = id => {
store.update(state => {
// Remove from custom component map if required
const component = state.mountedComponents[id]?.instance?.component
let customComponentMap = state.customComponentMap
if (component?.startsWith("plugin")) {
customComponentMap[component] = customComponentMap[component].filter(
x => {
return x !== id
}
)
}
// Remove from mounted components
delete state.mountedComponents[id]
return state
})
}
const isComponentRegistered = id => {
return get(store).mountedComponents[id] != null
}
const getComponentById = id => {
const asset = get(derivedStore).currentAsset
return findComponentById(asset?.props, id)
}
const getComponentDefinition = type => {
if (!type) {
return null
}
// Screenslot is an edge case
if (type === "screenslot") {
type = `${budibasePrefix}${type}`
} else if (type === "placeholder") {
return {}
}
// 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
} else if (type === "placeholder") {
return Placeholder
}
// 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, version }) => {
if (!Component || !schema?.schema?.name || !version) {
return
}
const component = `plugin/${schema.schema.name}`
store.update(state => {
state.customComponentManifest[component] = {
Component,
schema,
}
return state
})
// Reload any mounted instances of this custom component
const state = get(store)
if (state.customComponentMap[component]?.length) {
state.customComponentMap[component].forEach(id => {
state.mountedComponents[id]?.reload()
})
}
}
return {
...derivedStore,
actions: {
registerInstance,
unregisterInstance,
isComponentRegistered,
getComponentById,
getComponentDefinition,
getComponentConstructor,
registerCustomComponent,
},
}
}
export const componentStore = createComponentStore()