Merge pull request #15445 from Budibase/BUDI-9016/extract-componenterrors-from-client

Extract componenterrors from client
This commit is contained in:
Adria Navarro 2025-02-04 10:24:05 +01:00 committed by GitHub
commit 7c2375b078
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 319 additions and 258 deletions

View File

@ -4,6 +4,7 @@
import type { import type {
Component, Component,
ComponentCondition, ComponentCondition,
ComponentSetting,
EventHandler, EventHandler,
Screen, Screen,
} from "@budibase/types" } from "@budibase/types"
@ -21,7 +22,6 @@
processStringSync, processStringSync,
} from "@budibase/string-templates" } from "@budibase/string-templates"
import DrawerBindableInput from "@/components/common/bindings/DrawerBindableInput.svelte" import DrawerBindableInput from "@/components/common/bindings/DrawerBindableInput.svelte"
import { type ComponentSetting } from "@/stores/builder/components"
interface ComponentUsingState { interface ComponentUsingState {
id: string id: string

View File

@ -33,6 +33,8 @@ import {
import { BudiStore } from "../BudiStore" import { BudiStore } from "../BudiStore"
import { Utils } from "@budibase/frontend-core" import { Utils } from "@budibase/frontend-core"
import { import {
ComponentDefinition,
ComponentSetting,
Component as ComponentType, Component as ComponentType,
ComponentCondition, ComponentCondition,
FieldType, FieldType,
@ -55,30 +57,6 @@ export interface ComponentState {
selectedScreenId?: string | null selectedScreenId?: string | null
} }
export interface ComponentDefinition {
component: string
name: string
friendlyName?: string
hasChildren?: boolean
settings?: ComponentSetting[]
features?: Record<string, boolean>
typeSupportPresets?: Record<string, any>
legalDirectChildren: string[]
illegalChildren: string[]
}
export interface ComponentSetting {
key: string
type: string
label?: string
section?: string
name?: string
defaultValue?: any
selectAllFields?: boolean
resetOn?: string | string[]
settings?: ComponentSetting[]
}
export const INITIAL_COMPONENTS_STATE: ComponentState = { export const INITIAL_COMPONENTS_STATE: ComponentState = {
components: {}, components: {},
customComponents: [], customComponents: [],

View File

@ -6,14 +6,17 @@ import {
UIDatasourceType, UIDatasourceType,
Screen, Screen,
Component, Component,
UIComponentError,
ScreenProps, ScreenProps,
ComponentDefinition,
} from "@budibase/types" } from "@budibase/types"
import { queries } from "./queries" import { queries } from "./queries"
import { views } from "./views" import { views } from "./views"
import { bindings, featureFlag } from "@/helpers"
import { getBindableProperties } from "@/dataBinding"
import { componentStore, ComponentDefinition } from "./components"
import { findAllComponents } from "@/helpers/components" import { findAllComponents } from "@/helpers/components"
import { bindings } from "@/helpers"
import { getBindableProperties } from "@/dataBinding"
import { componentStore } from "./components"
import { getSettingsDefinition } from "@budibase/frontend-core"
function reduceBy<TItem extends {}, TKey extends keyof TItem>( function reduceBy<TItem extends {}, TKey extends keyof TItem>(
key: TKey, key: TKey,
@ -52,61 +55,10 @@ export const screenComponentErrors = derived(
$viewsV2, $viewsV2,
$queries, $queries,
$componentStore, $componentStore,
]): Record<string, string[]> => { ]): Record<string, UIComponentError[]> => {
if (!featureFlag.isEnabled("CHECK_COMPONENT_SETTINGS_ERRORS")) { if (!$selectedScreen) {
return {} return {}
} }
function getInvalidDatasources(
screen: Screen,
datasources: Record<string, any>
) {
const result: Record<string, string[]> = {}
for (const { component, setting } of findComponentsBySettingsType(
screen,
["table", "dataSource"],
$componentStore.components
)) {
const componentSettings = component[setting.key]
if (!componentSettings) {
continue
}
const { label } = componentSettings
const type = componentSettings.type as UIDatasourceType
const validationKey = validationKeyByType[type]
if (!validationKey) {
continue
}
const componentBindings = getBindableProperties(
$selectedScreen,
component._id
)
const componentDatasources = {
...reduceBy(
"rowId",
bindings.extractRelationships(componentBindings)
),
...reduceBy("value", bindings.extractFields(componentBindings)),
...reduceBy(
"value",
bindings.extractJSONArrayFields(componentBindings)
),
}
const resourceId = componentSettings[validationKey]
if (!{ ...datasources, ...componentDatasources }[resourceId]) {
const friendlyTypeName = friendlyNameByType[type] ?? type
result[component._id!] = [
`The ${friendlyTypeName} named "${label}" could not be found`,
]
}
}
return result
}
const datasources = { const datasources = {
...reduceBy("_id", $tables.list), ...reduceBy("_id", $tables.list),
@ -115,15 +67,169 @@ export const screenComponentErrors = derived(
...reduceBy("_id", $queries.list), ...reduceBy("_id", $queries.list),
} }
if (!$selectedScreen) { const { components: definitions } = $componentStore
// Skip validation if a screen is not selected.
return {}
}
return getInvalidDatasources($selectedScreen, datasources) const errors = {
...getInvalidDatasources($selectedScreen, datasources, definitions),
...getMissingAncestors($selectedScreen, definitions),
...getMissingRequiredSettings($selectedScreen, definitions),
}
return errors
} }
) )
function getInvalidDatasources(
screen: Screen,
datasources: Record<string, any>,
definitions: Record<string, ComponentDefinition>
) {
const result: Record<string, UIComponentError[]> = {}
for (const { component, setting } of findComponentsBySettingsType(
screen,
["table", "dataSource"],
definitions
)) {
const componentSettings = component[setting.key]
if (!componentSettings) {
continue
}
const { label } = componentSettings
const type = componentSettings.type as UIDatasourceType
const validationKey = validationKeyByType[type]
if (!validationKey) {
continue
}
const componentBindings = getBindableProperties(screen, component._id)
const componentDatasources = {
...reduceBy("rowId", bindings.extractRelationships(componentBindings)),
...reduceBy("value", bindings.extractFields(componentBindings)),
...reduceBy("value", bindings.extractJSONArrayFields(componentBindings)),
}
const resourceId = componentSettings[validationKey]
if (!{ ...datasources, ...componentDatasources }[resourceId]) {
const friendlyTypeName = friendlyNameByType[type] ?? type
result[component._id!] = [
{
key: setting.key,
message: `The ${friendlyTypeName} named "${label}" could not be found`,
errorType: "setting",
},
]
}
}
return result
}
function getMissingRequiredSettings(
screen: Screen,
definitions: Record<string, ComponentDefinition>
) {
const allComponents = findAllComponents(screen.props) as Component[]
const result: Record<string, UIComponentError[]> = {}
for (const component of allComponents) {
const definition = definitions[component._component]
const settings = getSettingsDefinition(definition)
const missingRequiredSettings = settings.filter((setting: any) => {
let empty =
component[setting.key] == null || component[setting.key] === ""
let missing = setting.required && empty
// Check if this setting depends on another, as it may not be required
if (setting.dependsOn) {
const dependsOnKey = setting.dependsOn.setting || setting.dependsOn
const dependsOnValue = setting.dependsOn.value
const realDependentValue = component[dependsOnKey]
const sectionDependsOnKey =
setting.sectionDependsOn?.setting || setting.sectionDependsOn
const sectionDependsOnValue = setting.sectionDependsOn?.value
const sectionRealDependentValue = component[sectionDependsOnKey]
if (dependsOnValue == null && realDependentValue == null) {
return false
}
if (dependsOnValue != null && dependsOnValue !== realDependentValue) {
return false
}
if (
sectionDependsOnValue != null &&
sectionDependsOnValue !== sectionRealDependentValue
) {
return false
}
}
return missing
})
if (missingRequiredSettings?.length) {
result[component._id!] = missingRequiredSettings.map((s: any) => ({
key: s.key,
message: `Add the <mark>${s.label}</mark> setting to start using your component`,
errorType: "setting",
}))
}
}
return result
}
const BudibasePrefix = "@budibase/standard-components/"
function getMissingAncestors(
screen: Screen,
definitions: Record<string, ComponentDefinition>
) {
const result: Record<string, UIComponentError[]> = {}
function checkMissingAncestors(component: Component, ancestors: string[]) {
for (const child of component._children || []) {
checkMissingAncestors(child, [...ancestors, component._component])
}
const definition = definitions[component._component]
if (!definition?.requiredAncestors?.length) {
return
}
const missingAncestors = definition.requiredAncestors.filter(
ancestor => !ancestors.includes(`${BudibasePrefix}${ancestor}`)
)
if (missingAncestors.length) {
const pluralise = (name: string) => {
return name.endsWith("s") ? `${name}'` : `${name}s`
}
result[component._id!] = missingAncestors.map(ancestor => {
const ancestorDefinition = definitions[`${BudibasePrefix}${ancestor}`]
return {
message: `${pluralise(definition.name)} need to be inside a
<mark>${ancestorDefinition.name}</mark>`,
errorType: "ancestor-setting",
ancestor: {
name: ancestorDefinition.name,
fullType: `${BudibasePrefix}${ancestor}`,
},
}
})
}
}
checkMissingAncestors(screen.props, [])
return result
}
export function findComponentsBySettingsType( export function findComponentsBySettingsType(
screen: Screen, screen: Screen,
type: string | string[], type: string | string[],
@ -149,10 +255,10 @@ export function findComponentsBySettingsType(
const setting = definition?.settings?.find((s: any) => const setting = definition?.settings?.find((s: any) =>
typesArray.includes(s.type) typesArray.includes(s.type)
) )
if (setting && "type" in setting) { if (setting) {
result.push({ result.push({
component, component,
setting: { type: setting.type!, key: setting.key! }, setting: { type: setting.type, key: setting.key },
}) })
} }
component._children?.forEach(child => { component._children?.forEach(child => {

View File

@ -19,8 +19,8 @@ import {
Screen, Screen,
Component, Component,
SaveScreenResponse, SaveScreenResponse,
ComponentDefinition,
} from "@budibase/types" } from "@budibase/types"
import { ComponentDefinition } from "./components"
interface ScreenState { interface ScreenState {
screens: Screen[] screens: Screen[]

View File

@ -11,11 +11,8 @@
<script> <script>
import { getContext, setContext, onMount } from "svelte" import { getContext, setContext, onMount } from "svelte"
import { writable, get } from "svelte/store" import { writable, get } from "svelte/store"
import { import { enrichProps, propsAreSame } from "utils/componentProps"
enrichProps, import { getSettingsDefinition } from "@budibase/frontend-core"
propsAreSame,
getSettingsDefinition,
} from "utils/componentProps"
import { import {
builderStore, builderStore,
devToolsStore, devToolsStore,
@ -29,7 +26,6 @@
import EmptyPlaceholder from "components/app/EmptyPlaceholder.svelte" import EmptyPlaceholder from "components/app/EmptyPlaceholder.svelte"
import ScreenPlaceholder from "components/app/ScreenPlaceholder.svelte" import ScreenPlaceholder from "components/app/ScreenPlaceholder.svelte"
import ComponentErrorState from "components/error-states/ComponentErrorState.svelte" import ComponentErrorState from "components/error-states/ComponentErrorState.svelte"
import { BudibasePrefix } from "../stores/components.js"
import { import {
decodeJSBinding, decodeJSBinding,
findHBSBlocks, findHBSBlocks,
@ -102,8 +98,6 @@
let definition let definition
let settingsDefinition let settingsDefinition
let settingsDefinitionMap let settingsDefinitionMap
let missingRequiredSettings = false
let componentErrors = false
// Temporary styles which can be added in the app preview for things like // Temporary styles which can be added in the app preview for things like
// DND. We clear these whenever a new instance is received. // DND. We clear these whenever a new instance is received.
@ -141,18 +135,11 @@
$: componentErrors = instance?._meta?.errors $: componentErrors = instance?._meta?.errors
$: hasChildren = !!definition?.hasChildren $: hasChildren = !!definition?.hasChildren
$: showEmptyState = definition?.showEmptyState !== false $: showEmptyState = definition?.showEmptyState !== false
$: hasMissingRequiredSettings = missingRequiredSettings?.length > 0 $: hasMissingRequiredSettings = !!componentErrors?.find(
e => e.errorType === "setting"
)
$: editable = !!definition?.editable && !hasMissingRequiredSettings $: editable = !!definition?.editable && !hasMissingRequiredSettings
$: hasComponentErrors = componentErrors?.length > 0 $: hasComponentErrors = componentErrors?.length > 0
$: requiredAncestors = definition?.requiredAncestors || []
$: missingRequiredAncestors = requiredAncestors.filter(
ancestor => !$component.ancestors.includes(`${BudibasePrefix}${ancestor}`)
)
$: hasMissingRequiredAncestors = missingRequiredAncestors?.length > 0
$: errorState =
hasMissingRequiredSettings ||
hasMissingRequiredAncestors ||
hasComponentErrors
// Interactive components can be selected, dragged and highlighted inside // Interactive components can be selected, dragged and highlighted inside
// the builder preview // the builder preview
@ -218,7 +205,7 @@
styles: normalStyles, styles: normalStyles,
draggable, draggable,
definition, definition,
errored: errorState, errored: hasComponentErrors,
} }
// When dragging and dropping, pad components to allow dropping between // When dragging and dropping, pad components to allow dropping between
@ -251,9 +238,8 @@
name, name,
editing, editing,
type: instance._component, type: instance._component,
errorState, errorState: hasComponentErrors,
parent: id, parent: id,
ancestors: [...($component?.ancestors ?? []), instance._component],
path: [...($component?.path ?? []), id], path: [...($component?.path ?? []), id],
darkMode, darkMode,
}) })
@ -310,40 +296,6 @@
staticSettings = instanceSettings.staticSettings staticSettings = instanceSettings.staticSettings
dynamicSettings = instanceSettings.dynamicSettings dynamicSettings = instanceSettings.dynamicSettings
// Check if we have any missing required settings
missingRequiredSettings = settingsDefinition.filter(setting => {
let empty = instance[setting.key] == null || instance[setting.key] === ""
let missing = setting.required && empty
// Check if this setting depends on another, as it may not be required
if (setting.dependsOn) {
const dependsOnKey = setting.dependsOn.setting || setting.dependsOn
const dependsOnValue = setting.dependsOn.value
const realDependentValue = instance[dependsOnKey]
const sectionDependsOnKey =
setting.sectionDependsOn?.setting || setting.sectionDependsOn
const sectionDependsOnValue = setting.sectionDependsOn?.value
const sectionRealDependentValue = instance[sectionDependsOnKey]
if (dependsOnValue == null && realDependentValue == null) {
return false
}
if (dependsOnValue != null && dependsOnValue !== realDependentValue) {
return false
}
if (
sectionDependsOnValue != null &&
sectionDependsOnValue !== sectionRealDependentValue
) {
return false
}
}
return missing
})
// When considering bindings we can ignore children, so we remove that // When considering bindings we can ignore children, so we remove that
// before storing the reference stringified version // before storing the reference stringified version
const noChildren = JSON.stringify({ ...instance, _children: null }) const noChildren = JSON.stringify({ ...instance, _children: null })
@ -686,7 +638,7 @@
class:pad class:pad
class:parent={hasChildren} class:parent={hasChildren}
class:block={isBlock} class:block={isBlock}
class:error={errorState} class:error={hasComponentErrors}
class:root={isRoot} class:root={isRoot}
data-id={id} data-id={id}
data-name={name} data-name={name}
@ -694,12 +646,8 @@
data-parent={$component.id} data-parent={$component.id}
use:gridLayout={gridMetadata} use:gridLayout={gridMetadata}
> >
{#if errorState} {#if hasComponentErrors}
<ComponentErrorState <ComponentErrorState {componentErrors} />
{missingRequiredSettings}
{missingRequiredAncestors}
{componentErrors}
/>
{:else} {:else}
<svelte:component this={constructor} bind:this={ref} {...initialSettings}> <svelte:component this={constructor} bind:this={ref} {...initialSettings}>
{#if children.length} {#if children.length}

View File

@ -1,8 +1,8 @@
<script> <script>
import { Layout, Toggle } from "@budibase/bbui" import { Layout, Toggle } from "@budibase/bbui"
import { getSettingsDefinition } from "@budibase/frontend-core"
import DevToolsStat from "./DevToolsStat.svelte" import DevToolsStat from "./DevToolsStat.svelte"
import { componentStore } from "stores/index.js" import { componentStore } from "stores/index.js"
import { getSettingsDefinition } from "utils/componentProps.js"
let showEnrichedSettings = true let showEnrichedSettings = true

View File

@ -1,21 +1,15 @@
<script lang="ts"> <script lang="ts">
import { getContext } from "svelte" import { getContext } from "svelte"
import { Icon } from "@budibase/bbui" import { Icon } from "@budibase/bbui"
import MissingRequiredSetting from "./MissingRequiredSetting.svelte" import { UIComponentError } from "@budibase/types"
import MissingRequiredAncestor from "./MissingRequiredAncestor.svelte" import ComponentErrorStateCta from "./ComponentErrorStateCTA.svelte"
export let missingRequiredSettings: export let componentErrors: UIComponentError[] | undefined
| { key: string; label: string }[]
| undefined
export let missingRequiredAncestors: string[] | undefined
export let componentErrors: string[] | undefined
const component = getContext("component") const component = getContext("component")
const { styleable, builderStore } = getContext("sdk") const { styleable, builderStore } = getContext("sdk")
$: styles = { ...$component.styles, normal: {}, custom: null, empty: true } $: styles = { ...$component.styles, normal: {}, custom: null, empty: true }
$: requiredSetting = missingRequiredSettings?.[0]
$: requiredAncestor = missingRequiredAncestors?.[0]
$: errorMessage = componentErrors?.[0] $: errorMessage = componentErrors?.[0]
</script> </script>
@ -23,12 +17,10 @@
{#if $component.errorState} {#if $component.errorState}
<div class="component-placeholder" use:styleable={styles}> <div class="component-placeholder" use:styleable={styles}>
<Icon name="Alert" color="var(--spectrum-global-color-static-red-600)" /> <Icon name="Alert" color="var(--spectrum-global-color-static-red-600)" />
{#if requiredAncestor} {#if errorMessage}
<MissingRequiredAncestor {requiredAncestor} /> <!-- eslint-disable-next-line svelte/no-at-html-tags-->
{:else if errorMessage} {@html errorMessage.message}
{errorMessage} <ComponentErrorStateCta error={errorMessage} />
{:else if requiredSetting}
<MissingRequiredSetting {requiredSetting} />
{/if} {/if}
</div> </div>
{/if} {/if}

View File

@ -0,0 +1,40 @@
<script lang="ts">
import { getContext } from "svelte"
import { UIComponentError } from "@budibase/types"
export let error: UIComponentError | undefined
const component = getContext("component")
const { builderStore } = getContext("sdk")
</script>
{#if error}
{#if error.errorType === "setting"}
<span>-</span>
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<span
class="spectrum-Link"
on:click={() => {
builderStore.actions.highlightSetting(error.key)
}}
>
Show me
</span>
{:else if error.errorType === "ancestor-setting"}
<span>-</span>
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<span
class="spectrum-Link"
on:click={() => {
builderStore.actions.addParentComponent(
$component.id,
error.ancestor.fullType
)
}}
>
Add {error.ancestor.name}
</span>
{/if}
{/if}

View File

@ -1,43 +0,0 @@
<script>
import { getContext } from "svelte"
import { BudibasePrefix } from "stores/components"
export let requiredAncestor
const component = getContext("component")
const { builderStore, componentStore } = getContext("sdk")
$: definition = componentStore.actions.getComponentDefinition($component.type)
$: fullAncestorType = `${BudibasePrefix}${requiredAncestor}`
$: ancestorDefinition =
componentStore.actions.getComponentDefinition(fullAncestorType)
$: pluralName = getPluralName(definition?.name, $component.type)
$: ancestorName = getAncestorName(ancestorDefinition?.name, requiredAncestor)
const getPluralName = (name, type) => {
if (!name) {
name = type.replace(BudibasePrefix, "")
}
return name.endsWith("s") ? `${name}'` : `${name}s`
}
const getAncestorName = name => {
return name || requiredAncestor
}
</script>
<span>
{pluralName} need to be inside a
<mark>{ancestorName}</mark>
</span>
<span>-</span>
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<span
class="spectrum-Link"
on:click={() => {
builderStore.actions.addParentComponent($component.id, fullAncestorType)
}}
>
Add {ancestorName}
</span>

View File

@ -1,22 +0,0 @@
<script>
import { getContext } from "svelte"
export let requiredSetting
const { builderStore } = getContext("sdk")
</script>
<span>
Add the <mark>{requiredSetting.label}</mark> setting to start using your component
</span>
<span>-</span>
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<span
class="spectrum-Link"
on:click={() => {
builderStore.actions.highlightSetting(requiredSetting.key)
}}
>
Show me
</span>

View File

@ -11,7 +11,15 @@ export interface SDK {
generateGoldenSample: any generateGoldenSample: any
builderStore: Readable<{ builderStore: Readable<{
inBuilder: boolean inBuilder: boolean
}> }> & {
actions: {
highlightSetting: (key: string) => void
addParentComponent: (
componentId: string,
fullAncestorType: string
) => void
}
}
} }
export type Component = Readable<{ export type Component = Readable<{

View File

@ -97,26 +97,3 @@ export const propsUseBinding = (props, bindingKey) => {
} }
return false return false
} }
/**
* Gets the definition of this component's settings from the manifest
*/
export const getSettingsDefinition = definition => {
if (!definition) {
return []
}
let settings = []
definition.settings?.forEach(setting => {
if (setting.section) {
settings = settings.concat(
(setting.settings || [])?.map(childSetting => ({
...childSetting,
sectionDependsOn: setting.dependsOn,
}))
)
} else {
settings.push(setting)
}
})
return settings
}

View File

@ -0,0 +1,26 @@
import { ComponentDefinition, ComponentSetting } from "@budibase/types"
/**
* Gets the definition of this component's settings from the manifest
*/
export const getSettingsDefinition = (
definition: ComponentDefinition
): ComponentSetting[] => {
if (!definition) {
return []
}
let settings: ComponentSetting[] = []
definition.settings?.forEach(setting => {
if (setting.section) {
settings = settings.concat(
(setting.settings || [])?.map(childSetting => ({
...childSetting,
sectionDependsOn: setting.dependsOn,
}))
)
} else {
settings.push(setting)
}
})
return settings
}

View File

@ -13,3 +13,4 @@ export * from "./download"
export * from "./settings" export * from "./settings"
export * from "./relatedColumns" export * from "./relatedColumns"
export * from "./table" export * from "./table"
export * from "./components"

View File

@ -1,6 +1,5 @@
export enum FeatureFlag { export enum FeatureFlag {
USE_ZOD_VALIDATOR = "USE_ZOD_VALIDATOR", USE_ZOD_VALIDATOR = "USE_ZOD_VALIDATOR",
CHECK_COMPONENT_SETTINGS_ERRORS = "CHECK_COMPONENT_SETTINGS_ERRORS",
// Account-portal // Account-portal
DIRECT_LOGIN_TO_ACCOUNT_PORTAL = "DIRECT_LOGIN_TO_ACCOUNT_PORTAL", DIRECT_LOGIN_TO_ACCOUNT_PORTAL = "DIRECT_LOGIN_TO_ACCOUNT_PORTAL",
@ -8,7 +7,6 @@ export enum FeatureFlag {
export const FeatureFlagDefaults = { export const FeatureFlagDefaults = {
[FeatureFlag.USE_ZOD_VALIDATOR]: false, [FeatureFlag.USE_ZOD_VALIDATOR]: false,
[FeatureFlag.CHECK_COMPONENT_SETTINGS_ERRORS]: false,
// Account-portal // Account-portal
[FeatureFlag.DIRECT_LOGIN_TO_ACCOUNT_PORTAL]: false, [FeatureFlag.DIRECT_LOGIN_TO_ACCOUNT_PORTAL]: false,

View File

@ -0,0 +1,20 @@
interface BaseUIComponentError {
message: string
}
interface UISettingComponentError extends BaseUIComponentError {
errorType: "setting"
key: string
}
interface UIAncestorComponentError extends BaseUIComponentError {
errorType: "ancestor-setting"
ancestor: {
name: string
fullType: string
}
}
export type UIComponentError =
| UISettingComponentError
| UIAncestorComponentError

View File

@ -1,2 +1,34 @@
export * from "./sidepanel" export * from "./sidepanel"
export * from "./codeEditor" export * from "./codeEditor"
export * from "./errors"
export interface ComponentDefinition {
component: string
name: string
friendlyName?: string
hasChildren?: boolean
settings?: ComponentSetting[]
features?: Record<string, boolean>
typeSupportPresets?: Record<string, any>
legalDirectChildren: string[]
requiredAncestors?: string[]
illegalChildren: string[]
}
export interface ComponentSetting {
key: string
type: string
label?: string
section?: string
name?: string
defaultValue?: any
selectAllFields?: boolean
resetOn?: string | string[]
settings?: ComponentSetting[]
dependsOn?:
| string
| {
setting: string
value: string
}
}