diff --git a/lerna.json b/lerna.json index d9d9e01bef..09c739cc8c 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", - "version": "3.4.11", + "version": "3.4.12", "npmClient": "yarn", "concurrency": 20, "command": { diff --git a/packages/builder/src/components/design/settings/controls/URLVariableTestInput.svelte b/packages/builder/src/components/design/settings/controls/URLVariableTestInput.svelte index a75d7e625d..6a6cb081ef 100644 --- a/packages/builder/src/components/design/settings/controls/URLVariableTestInput.svelte +++ b/packages/builder/src/components/design/settings/controls/URLVariableTestInput.svelte @@ -2,7 +2,7 @@ import { onMount } from "svelte" import { Input, Label } from "@budibase/bbui" import { previewStore, selectedScreen } from "@/stores/builder" - import type { ComponentContext } from "@budibase/types" + import type { AppContext } from "@budibase/types" export let baseRoute = "" @@ -31,7 +31,7 @@ // This function is needed to repopulate the test value from componentContext // when a user navigates to another component and then back again - const updateTestValueFromContext = (context: ComponentContext | null) => { + const updateTestValueFromContext = (context: AppContext | null) => { if (context?.url && !testValue) { const { wild, ...urlParams } = context.url const queryParams = context.query diff --git a/packages/builder/src/helpers/components.js b/packages/builder/src/helpers/components.ts similarity index 77% rename from packages/builder/src/helpers/components.js rename to packages/builder/src/helpers/components.ts index b1116d7750..074f74f06d 100644 --- a/packages/builder/src/helpers/components.js +++ b/packages/builder/src/helpers/components.ts @@ -8,27 +8,32 @@ import { } from "@budibase/string-templates" import { capitalise } from "@/helpers" import { Constants } from "@budibase/frontend-core" +import { Component, ComponentContext } from "@budibase/types" const { ContextScopes } = Constants /** * Recursively searches for a specific component ID */ -export const findComponent = (rootComponent, id) => { +export const findComponent = (rootComponent: Component, id: string) => { return searchComponentTree(rootComponent, comp => comp._id === id) } /** * Recursively searches for a specific component type */ -export const findComponentType = (rootComponent, type) => { +export const findComponentType = (rootComponent: Component, type: string) => { return searchComponentTree(rootComponent, comp => comp._component === type) } /** * Recursively searches for the parent component of a specific component ID */ -export const findComponentParent = (rootComponent, id, parentComponent) => { +export const findComponentParent = ( + rootComponent: Component | undefined, + id: string | undefined, + parentComponent: Component | null = null +): Component | null => { if (!rootComponent || !id) { return null } @@ -51,7 +56,11 @@ export const findComponentParent = (rootComponent, id, parentComponent) => { * Recursively searches for a specific component ID and records the component * path to this component */ -export const findComponentPath = (rootComponent, id, path = []) => { +export const findComponentPath = ( + rootComponent: Component, + id: string | undefined, + path: Component[] = [] +): Component[] => { if (!rootComponent || !id) { return [] } @@ -75,11 +84,14 @@ export const findComponentPath = (rootComponent, id, path = []) => { * Recurses through the component tree and finds all components which match * a certain selector */ -export const findAllMatchingComponents = (rootComponent, selector) => { +export const findAllMatchingComponents = ( + rootComponent: Component | null, + selector: (component: Component) => boolean +) => { if (!rootComponent || !selector) { return [] } - let components = [] + let components: Component[] = [] if (rootComponent._children) { rootComponent._children.forEach(child => { components = [ @@ -97,7 +109,7 @@ export const findAllMatchingComponents = (rootComponent, selector) => { /** * Recurses through the component tree and finds all components. */ -export const findAllComponents = rootComponent => { +export const findAllComponents = (rootComponent: Component) => { return findAllMatchingComponents(rootComponent, () => true) } @@ -105,9 +117,9 @@ export const findAllComponents = rootComponent => { * Finds the closest parent component which matches certain criteria */ export const findClosestMatchingComponent = ( - rootComponent, - componentId, - selector + rootComponent: Component, + componentId: string | undefined, + selector: (component: Component) => boolean ) => { if (!selector) { return null @@ -125,7 +137,10 @@ export const findClosestMatchingComponent = ( * Recurses through a component tree evaluating a matching function against * components until a match is found */ -const searchComponentTree = (rootComponent, matchComponent) => { +const searchComponentTree = ( + rootComponent: Component, + matchComponent: (component: Component) => boolean +): Component | null => { if (!rootComponent || !matchComponent) { return null } @@ -150,15 +165,18 @@ const searchComponentTree = (rootComponent, matchComponent) => { * This mutates the object in place. * @param component the component to randomise */ -export const makeComponentUnique = component => { +export const makeComponentUnique = (component: Component) => { if (!component) { return } // Generate a full set of component ID replacements in this tree - const idReplacements = [] - const generateIdReplacements = (component, replacements) => { - const oldId = component._id + const idReplacements: [string, string][] = [] + const generateIdReplacements = ( + component: Component, + replacements: [string, string][] + ) => { + const oldId = component._id! const newId = Helpers.uuid() replacements.push([oldId, newId]) component._children?.forEach(x => generateIdReplacements(x, replacements)) @@ -182,9 +200,9 @@ export const makeComponentUnique = component => { let js = decodeJSBinding(sanitizedBinding) if (js != null) { // Replace ID inside JS binding - idReplacements.forEach(([oldId, newId]) => { + for (const [oldId, newId] of idReplacements) { js = js.replace(new RegExp(oldId, "g"), newId) - }) + } // Create new valid JS binding let newBinding = encodeJSBinding(js) @@ -204,7 +222,7 @@ export const makeComponentUnique = component => { return JSON.parse(definition) } -export const getComponentText = component => { +export const getComponentText = (component: Component) => { if (component == null) { return "" } @@ -218,7 +236,7 @@ export const getComponentText = component => { return capitalise(type) } -export const getComponentName = component => { +export const getComponentName = (component: Component) => { if (component == null) { return "" } @@ -229,9 +247,9 @@ export const getComponentName = component => { } // Gets all contexts exposed by a certain component type, including actions -export const getComponentContexts = component => { +export const getComponentContexts = (component: string) => { const def = componentStore.getDefinition(component) - let contexts = [] + let contexts: ComponentContext[] = [] if (def?.context) { contexts = Array.isArray(def.context) ? [...def.context] : [def.context] } @@ -251,9 +269,9 @@ export const getComponentContexts = component => { * Recurses through the component tree and builds a tree of contexts provided * by components. */ -export const buildContextTree = ( - rootComponent, - tree = { root: [] }, +const buildContextTree = ( + rootComponent: Component, + tree: Record = { root: [] }, currentBranch = "root" ) => { // Sanity check @@ -264,12 +282,12 @@ export const buildContextTree = ( // Process this component's contexts const contexts = getComponentContexts(rootComponent._component) if (contexts.length) { - tree[currentBranch].push(rootComponent._id) + tree[currentBranch].push(rootComponent._id!) // If we provide local context, start a new branch for our children if (contexts.some(context => context.scope === ContextScopes.Local)) { - currentBranch = rootComponent._id - tree[rootComponent._id] = [] + currentBranch = rootComponent._id! + tree[rootComponent._id!] = [] } } @@ -287,9 +305,9 @@ export const buildContextTree = ( * Generates a lookup map of which context branch all components in a component * tree are inside. */ -export const buildContextTreeLookupMap = rootComponent => { +export const buildContextTreeLookupMap = (rootComponent: Component) => { const tree = buildContextTree(rootComponent) - let map = {} + const map: Record = {} Object.entries(tree).forEach(([branch, ids]) => { ids.forEach(id => { map[id] = branch @@ -299,9 +317,9 @@ export const buildContextTreeLookupMap = rootComponent => { } // Get a flat list of ids for all descendants of a component -export const getChildIdsForComponent = component => { +export const getChildIdsForComponent = (component: Component): string[] => { return [ - component._id, + component._id!, ...(component?._children ?? []).map(getChildIdsForComponent).flat(1), ] } diff --git a/packages/builder/src/stores/builder/componentTreeNodes.ts b/packages/builder/src/stores/builder/componentTreeNodes.ts index b88563de6a..0e4dd5738b 100644 --- a/packages/builder/src/stores/builder/componentTreeNodes.ts +++ b/packages/builder/src/stores/builder/componentTreeNodes.ts @@ -58,7 +58,7 @@ export class ComponentTreeNodesStore extends BudiStore { const path = findComponentPath(selectedScreen.props, componentId) - const componentIds = path.map((component: Component) => component._id) + const componentIds = path.map((component: Component) => component._id!) this.update((openNodes: OpenNodesState) => { const newNodes = Object.fromEntries( diff --git a/packages/builder/src/stores/builder/components.ts b/packages/builder/src/stores/builder/components.ts index 38fa9a6a41..e141de01ac 100644 --- a/packages/builder/src/stores/builder/components.ts +++ b/packages/builder/src/stores/builder/components.ts @@ -1,3 +1,5 @@ +// TODO: analise and fix all the undefined ! and ? + import { get, derived } from "svelte/store" import { cloneDeep } from "lodash/fp" import { API } from "@/api" @@ -36,7 +38,7 @@ import { Utils } from "@budibase/frontend-core" import { ComponentDefinition, ComponentSetting, - Component as ComponentType, + Component, ComponentCondition, FieldType, Screen, @@ -45,10 +47,6 @@ import { import { utils } from "@budibase/shared-core" import { getSequentialName } from "@/helpers/duplicate" -interface Component extends ComponentType { - _id: string -} - export interface ComponentState { components: Record customComponents: string[] @@ -182,7 +180,7 @@ export class ComponentStore extends BudiStore { * Takes an enriched component instance and applies any required migration * logic */ - migrateSettings(enrichedComponent: Component) { + migrateSettings(enrichedComponent: Component | null) { const componentPrefix = "@budibase/standard-components" let migrated = false @@ -232,7 +230,7 @@ export class ComponentStore extends BudiStore { enrichEmptySettings( component: Component, - opts: { screen?: Screen; parent?: Component; useDefaultValues?: boolean } + opts: { screen?: Screen; parent?: string; useDefaultValues?: boolean } ) { if (!component?._component) { return @@ -240,7 +238,7 @@ export class ComponentStore extends BudiStore { const defaultDS = this.getDefaultDatasource() const settings = this.getComponentSettings(component._component) const { parent, screen, useDefaultValues } = opts || {} - const treeId = parent?._id || component._id + const treeId = parent || component._id if (!screen) { return } @@ -425,7 +423,7 @@ export class ComponentStore extends BudiStore { createInstance( componentType: string, presetProps?: Record, - parent?: Component + parent?: string ): Component | null { const screen = get(selectedScreen) if (!screen) { @@ -503,7 +501,7 @@ export class ComponentStore extends BudiStore { async create( componentType: string, presetProps?: Record, - parent?: Component, + parent?: string, index?: number ) { const state = get(this.store) @@ -519,7 +517,7 @@ export class ComponentStore extends BudiStore { // Insert in position if specified if (parent && index != null) { await screenStore.patch((screen: Screen) => { - let parentComponent = findComponent(screen.props, parent) + let parentComponent = findComponent(screen.props, parent)! if (!parentComponent._children?.length) { parentComponent._children = [componentInstance] } else { @@ -538,7 +536,7 @@ export class ComponentStore extends BudiStore { } const currentComponent = findComponent( screen.props, - selectedComponentId + selectedComponentId! ) if (!currentComponent) { return false @@ -581,7 +579,7 @@ export class ComponentStore extends BudiStore { return state }) - componentTreeNodesStore.makeNodeVisible(componentInstance._id) + componentTreeNodesStore.makeNodeVisible(componentInstance._id!) // Log event analytics.captureEvent(Events.COMPONENT_CREATED, { @@ -633,7 +631,7 @@ export class ComponentStore extends BudiStore { // Determine the next component to select, and select it before deletion // to avoid an intermediate state of no component selection const state = get(this.store) - let nextId = "" + let nextId: string | null = "" if (state.selectedComponentId === component._id) { nextId = this.getNext() if (!nextId) { @@ -646,7 +644,7 @@ export class ComponentStore extends BudiStore { nextId = nextId.replace("-navigation", "-screen") } this.update(state => { - state.selectedComponentId = nextId + state.selectedComponentId = nextId ?? undefined return state }) } @@ -654,18 +652,18 @@ export class ComponentStore extends BudiStore { // Patch screen await screenStore.patch((screen: Screen) => { // Check component exists - component = findComponent(screen.props, component._id) - if (!component) { + const updatedComponent = findComponent(screen.props, component._id!) + if (!updatedComponent) { return false } // Check component has a valid parent - const parent = findComponentParent(screen.props, component._id) + const parent = findComponentParent(screen.props, updatedComponent._id) if (!parent) { return false } - parent._children = parent._children.filter( - (child: Component) => child._id !== component._id + parent._children = parent._children!.filter( + (child: Component) => child._id !== updatedComponent._id ) }, null) } @@ -729,7 +727,7 @@ export class ComponentStore extends BudiStore { // Patch screen const patch = (screen: Screen) => { // Get up to date ref to target - targetComponent = findComponent(screen.props, targetComponent._id) + targetComponent = findComponent(screen.props, targetComponent!._id!)! if (!targetComponent) { return false } @@ -743,7 +741,7 @@ export class ComponentStore extends BudiStore { if (!cut) { componentToPaste = makeComponentUnique(componentToPaste) } - newComponentId = componentToPaste._id + newComponentId = componentToPaste._id! // Strip grid position metadata if pasting into a new screen, but keep // alignment metadata @@ -820,8 +818,8 @@ export class ComponentStore extends BudiStore { if (!screen) { throw "A valid screen must be selected" } - const parent = findComponentParent(screen.props, componentId) - const index = parent?._children.findIndex( + const parent = findComponentParent(screen.props, componentId)! + const index = parent?._children?.findIndex( (x: Component) => x._id === componentId ) @@ -839,29 +837,29 @@ export class ComponentStore extends BudiStore { } // If we have siblings above us, choose the sibling or a descendant - if (index > 0) { + if (index !== undefined && index > 0) { // If sibling before us accepts children, and is not collapsed, select a descendant - const previousSibling = parent._children[index - 1] + const previousSibling = parent._children![index - 1] if ( previousSibling._children?.length && - componentTreeNodesStore.isNodeExpanded(previousSibling._id) + componentTreeNodesStore.isNodeExpanded(previousSibling._id!) ) { let target = previousSibling while ( target._children?.length && - componentTreeNodesStore.isNodeExpanded(target._id) + componentTreeNodesStore.isNodeExpanded(target._id!) ) { target = target._children[target._children.length - 1] } - return target._id + return target._id! } // Otherwise just select sibling - return previousSibling._id + return previousSibling._id! } // If no siblings above us, select the parent - return parent._id + return parent._id! } getNext() { @@ -873,9 +871,9 @@ export class ComponentStore extends BudiStore { throw "A valid screen must be selected" } const parent = findComponentParent(screen.props, componentId) - const index = parent?._children.findIndex( + const index = parent?._children?.findIndex( (x: Component) => x._id === componentId - ) + )! // Check for screen and navigation component edge cases const screenComponentId = `${screen._id}-screen` @@ -888,37 +886,38 @@ export class ComponentStore extends BudiStore { if ( component?._children?.length && (state.selectedComponentId === navComponentId || - componentTreeNodesStore.isNodeExpanded(component._id)) + componentTreeNodesStore.isNodeExpanded(component._id!)) ) { - return component._children[0]._id + return component._children[0]._id! } else if (!parent) { return null } // Otherwise select the next sibling if we have one - if (index < parent._children.length - 1) { - const nextSibling = parent._children[index + 1] - return nextSibling._id + if (index < parent._children!.length - 1) { + const nextSibling = parent._children![index + 1] + return nextSibling._id! } // Last child, select our parents next sibling let target = parent let targetParent = findComponentParent(screen.props, target._id) - let targetIndex = targetParent?._children.findIndex( + let targetIndex = targetParent?._children?.findIndex( (child: Component) => child._id === target._id - ) + )! while ( targetParent != null && - targetIndex === targetParent._children?.length - 1 + targetParent._children && + targetIndex === targetParent._children.length - 1 ) { target = targetParent targetParent = findComponentParent(screen.props, target._id) - targetIndex = targetParent?._children.findIndex( + targetIndex = targetParent?._children!.findIndex( (child: Component) => child._id === target._id - ) + )! } if (targetParent) { - return targetParent._children[targetIndex + 1]._id + return targetParent._children![targetIndex + 1]._id! } else { return null } @@ -950,16 +949,16 @@ export class ComponentStore extends BudiStore { const parent = findComponentParent(screen.props, componentId) // Check we aren't right at the top of the tree - const index = parent?._children.findIndex( + const index = parent?._children?.findIndex( (x: Component) => x._id === componentId - ) + )! if (!parent || (index === 0 && parent._id === screen.props._id)) { return } // Copy original component and remove it from the parent - const originalComponent = cloneDeep(parent._children[index]) - parent._children = parent._children.filter( + const originalComponent = cloneDeep(parent._children![index]) + parent._children = parent._children!.filter( (component: Component) => component._id !== componentId ) @@ -971,9 +970,9 @@ export class ComponentStore extends BudiStore { const definition = this.getDefinition(previousSibling._component) if ( definition?.hasChildren && - componentTreeNodesStore.isNodeExpanded(previousSibling._id) + componentTreeNodesStore.isNodeExpanded(previousSibling._id!) ) { - previousSibling._children.push(originalComponent) + previousSibling._children!.push(originalComponent) } // Otherwise just move component above sibling @@ -985,11 +984,11 @@ export class ComponentStore extends BudiStore { // If no siblings above us, go above the parent as long as it isn't // the screen else if (parent._id !== screen.props._id) { - const grandParent = findComponentParent(screen.props, parent._id) - const parentIndex = grandParent._children.findIndex( + const grandParent = findComponentParent(screen.props, parent._id)! + const parentIndex = grandParent._children!.findIndex( (child: Component) => child._id === parent._id ) - grandParent._children.splice(parentIndex, 0, originalComponent) + grandParent._children!.splice(parentIndex, 0, originalComponent) } }, null) } @@ -1028,9 +1027,9 @@ export class ComponentStore extends BudiStore { const definition = this.getDefinition(nextSibling._component) if ( definition?.hasChildren && - componentTreeNodesStore.isNodeExpanded(nextSibling._id) + componentTreeNodesStore.isNodeExpanded(nextSibling._id!) ) { - nextSibling._children.splice(0, 0, originalComponent) + nextSibling._children!.splice(0, 0, originalComponent) } // Otherwise move below next sibling @@ -1041,11 +1040,11 @@ export class ComponentStore extends BudiStore { // Last child, so move below our parent else { - const grandParent = findComponentParent(screen.props, parent._id) - const parentIndex = grandParent._children.findIndex( + const grandParent = findComponentParent(screen.props, parent._id)! + const parentIndex = grandParent._children!.findIndex( (child: Component) => child._id === parent._id ) - grandParent._children.splice(parentIndex + 1, 0, originalComponent) + grandParent._children!.splice(parentIndex + 1, 0, originalComponent) } }, null) } @@ -1208,13 +1207,13 @@ export class ComponentStore extends BudiStore { } // Replace component with parent - const index = oldParentDefinition._children.findIndex( + const index = oldParentDefinition._children!.findIndex( (component: Component) => component._id === componentId ) if (index === -1) { return false } - oldParentDefinition._children[index] = { + oldParentDefinition._children![index] = { ...newParentDefinition, _children: [definition], } diff --git a/packages/builder/src/stores/builder/preview.ts b/packages/builder/src/stores/builder/preview.ts index 0fef91d6b9..d95903b3a3 100644 --- a/packages/builder/src/stores/builder/preview.ts +++ b/packages/builder/src/stores/builder/preview.ts @@ -1,6 +1,6 @@ import { get } from "svelte/store" import { BudiStore } from "../BudiStore" -import { PreviewDevice, ComponentContext } from "@budibase/types" +import { PreviewDevice, ComponentContext, AppContext } from "@budibase/types" type PreviewEventHandler = (name: string, payload?: any) => void @@ -8,7 +8,7 @@ interface PreviewState { previewDevice: PreviewDevice previewEventHandler: PreviewEventHandler | null showPreview: boolean - selectedComponentContext: ComponentContext | null + selectedComponentContext: AppContext | null } const INITIAL_PREVIEW_STATE: PreviewState = { diff --git a/packages/builder/src/stores/builder/screens.ts b/packages/builder/src/stores/builder/screens.ts index b7d9a8be30..51072adbb8 100644 --- a/packages/builder/src/stores/builder/screens.ts +++ b/packages/builder/src/stores/builder/screens.ts @@ -490,7 +490,7 @@ export class ScreenStore extends BudiStore { // Flatten the recursive component tree const components = findAllMatchingComponents( screen.props, - (x: Component) => x + (x: Component) => !!x ) // Iterate over all components and run checks diff --git a/packages/frontend-core/src/constants.ts b/packages/frontend-core/src/constants.ts index 907d91825f..3e51719bfd 100644 --- a/packages/frontend-core/src/constants.ts +++ b/packages/frontend-core/src/constants.ts @@ -112,10 +112,7 @@ export const EventPublishType = { ENV_VAR_UPGRADE_PANEL_OPENED: "environment_variable_upgrade_panel_opened", } -export const ContextScopes = { - Local: "local", - Global: "global", -} +export { ComponentContextScopes as ContextScopes } from "@budibase/types" export const TypeIconMap = { [FieldType.STRING]: "Text", diff --git a/packages/types/src/ui/components/index.ts b/packages/types/src/ui/components/index.ts index f477ed2bd3..c34174120c 100644 --- a/packages/types/src/ui/components/index.ts +++ b/packages/types/src/ui/components/index.ts @@ -28,6 +28,8 @@ export interface ComponentDefinition { width: number height: number } + context?: ComponentContext | ComponentContext[] + actions?: (string | any)[] } export type DependsOnComponentSetting = @@ -56,3 +58,28 @@ export interface ComponentSetting { self: boolean } } +interface ComponentAction { + type: string + suffix?: string +} + +interface ComponentStaticContextValue { + label: string + key: string + type: string // technically this is a long list of options but there are too many to enumerate +} + +export interface ComponentContext { + type: ComponentContextType + scope?: ComponentContextScopes + actions?: ComponentAction[] + suffix?: string + values?: ComponentStaticContextValue[] +} + +export type ComponentContextType = "action" | "static" | "schema" | "form" + +export const enum ComponentContextScopes { + Local = "local", + Global = "global", +} diff --git a/packages/types/src/ui/stores/preview.ts b/packages/types/src/ui/stores/preview.ts index d9f5f2ac46..75309f5dda 100644 --- a/packages/types/src/ui/stores/preview.ts +++ b/packages/types/src/ui/stores/preview.ts @@ -1,2 +1,3 @@ export type PreviewDevice = "desktop" | "tablet" | "mobile" -export type ComponentContext = Record + +export type AppContext = Record