1
0
Fork 0
mirror of synced 2024-09-09 14:11:30 +12:00
budibase/packages/client/src/stores/components.js
Andrew Kingston f2b12bcf45 Component error state improvements (#10136)
* Tidy logic for creating initial component instances

* Add initial implementation of enriching empty settings

* Fix regression that prevented custom placeholders from working (#9994)

* Tidy up

* Add automatic naming of form fields when added

* Update missing required setting placeholder

* Improve error states and add ability to automatically wrap a component in a required parent type

* Fix crash in column editor and rename component placeholder to error state

* Select the parent component after adding it when wrapping a component with a missing ancestor

* Fix blocks and make fields require forms

* Improve empty component placeholder

* Lint
2023-03-28 21:11:33 +01:00

188 lines
5.2 KiB
JavaScript

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 * as AppComponents from "../components/app/index.js"
import { ScreenslotType } from "../constants.js"
export const BudibasePrefix = "@budibase/standard-components/"
const createComponentStore = () => {
const store = writable({
customComponentManifest: {},
customComponentMap: {},
mountedComponents: {},
})
const derivedStore = derived(
[store, builderStore, devToolsStore, screenStore],
([$store, $builderStore, $devToolsStore, $screenStore]) => {
const { inBuilder, selectedComponentId } = $builderStore
// Avoid any of this logic if we aren't in the builder preview
if (!inBuilder && !$devToolsStore.visible) {
return {}
}
const root = $screenStore.activeScreen?.props
const component = findComponentById(root, selectedComponentId)
const definition = getComponentDefinition(component?._component)
// Derive the selected component path
const selectedPath =
findComponentPathById(root, selectedComponentId) || []
return {
customComponentManifest: $store.customComponentManifest,
selectedComponentInstance:
$store.mountedComponents[selectedComponentId],
selectedComponent: component,
selectedComponentDefinition: definition,
selectedComponentPath: selectedPath?.map(component => component._id),
mountedComponentCount: Object.keys($store.mountedComponents).length,
}
}
)
const registerInstance = (id, instance) => {
if (!id) {
return
}
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 => {
if (!id) {
return
}
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 root = get(screenStore).activeScreen?.props
return findComponentById(root, id)
}
const getComponentDefinition = type => {
if (!type) {
return null
}
// Screenslot is an edge case
if (type === ScreenslotType) {
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 === ScreenslotType) {
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 getComponentInstance = id => {
if (!id) {
return null
}
return derived(store, $store => $store.mountedComponents[id])
}
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,
getComponentInstance,
registerCustomComponent,
},
}
}
export const componentStore = createComponentStore()