diff --git a/packages/ui/src/components/Visualization/Canvas/Canvas.test.tsx b/packages/ui/src/components/Visualization/Canvas/Canvas.test.tsx index 97a80a3ca..769c256cb 100644 --- a/packages/ui/src/components/Visualization/Canvas/Canvas.test.tsx +++ b/packages/ui/src/components/Visualization/Canvas/Canvas.test.tsx @@ -39,7 +39,7 @@ describe('Canvas', () => { it('should render the Catalog button if `CatalogModalContext` is provided', async () => { const result = render( - + diff --git a/packages/ui/src/components/Visualization/Custom/CustomNode.tsx b/packages/ui/src/components/Visualization/Custom/CustomNode.tsx index 560cfd4e7..ed1f8376c 100644 --- a/packages/ui/src/components/Visualization/Custom/CustomNode.tsx +++ b/packages/ui/src/components/Visualization/Custom/CustomNode.tsx @@ -1,4 +1,4 @@ -import { MinusIcon } from '@patternfly/react-icons'; +import { AngleDownIcon, AngleUpIcon, MinusIcon, PlusIcon } from '@patternfly/react-icons'; import { ContextMenuItem, DefaultNode, @@ -10,7 +10,8 @@ import { withContextMenu, withSelection, } from '@patternfly/react-topology'; -import { FunctionComponent, useCallback, useContext } from 'react'; +import { FunctionComponent, PropsWithChildren, useCallback, useContext, useMemo } from 'react'; +import { CatalogModalContext } from '../../../providers/catalog-modal.provider'; import { EntitiesContext } from '../../../providers/entities.provider'; import { CanvasNode } from '../Canvas/canvas.models'; import { CanvasService } from '../Canvas/canvas.service'; @@ -46,6 +47,46 @@ const CustomNode: FunctionComponent = ({ element, ...rest }) => ); }; +const AddNode: FunctionComponent> = (props) => { + const entitiesContext = useContext(EntitiesContext); + const catalogModalContext = useContext(CatalogModalContext); + const element: GraphElement = useContext(ElementContext); + const vizNode = element.getData()?.vizNode; + const shouldRender = useMemo(() => { + const isRootNode = vizNode?.getParentNode() === undefined && vizNode?.getPreviousNode() === undefined; + + /** If this is the root node and this component is in `prepend` mode, we should skip rendering */ + return !(isRootNode && props.mode === 'prepend'); + }, [props.mode, vizNode]); + + const onAddNode = useCallback(async () => { + /** Get compatible nodes and the location where can be introduced */ + + /** Prepare Catalog filter to only show the compatible nodes */ + + /** Open Catalog modal */ + const newComponent = await catalogModalContext?.getNewComponent(); + if (!newComponent || !vizNode || !entitiesContext) { + return; + } + + /** Add new node to the entities */ + const defaultDefinition = entitiesContext.camelResource.getDefaultNodeDefition(newComponent); + + /** Add new node to the canvas */ + if (props.mode === 'prepend') { + vizNode.prependBaseEntityChild(defaultDefinition); + } else { + vizNode.appendBaseEntityChild(defaultDefinition); + } + + /** Update entity */ + entitiesContext.updateCodeFromEntities(); + }, [catalogModalContext, entitiesContext, props.mode, vizNode]); + + return shouldRender ? {props.children} : null; +}; + const RemoveNode: FunctionComponent = () => { const entitiesContext = useContext(EntitiesContext); const element: GraphElement = useContext(ElementContext); @@ -64,5 +105,13 @@ const RemoveNode: FunctionComponent = () => { }; export const CustomNodeWithSelection: typeof DefaultNode = withContextMenu(() => [ + + + Prepend node + , + + + Append node + , , ])(withSelection()(CustomNode) as typeof DefaultNode) as typeof DefaultNode; diff --git a/packages/ui/src/models/camel-catalog-index.ts b/packages/ui/src/models/camel-catalog-index.ts index 618152bf9..0f9d5208e 100644 --- a/packages/ui/src/models/camel-catalog-index.ts +++ b/packages/ui/src/models/camel-catalog-index.ts @@ -26,7 +26,7 @@ export interface SchemaEntry extends CatalogEntry { description: string; } -export type CatalogTypes = Record; +export type ComponentsCatalogTypes = ICamelComponentDefinition | ICamelProcessorDefinition | IKameletDefinition; export interface ComponentsCatalog { [CatalogKind.Component]?: Record; diff --git a/packages/ui/src/models/camel-component-lookup.ts b/packages/ui/src/models/camel-component-lookup.ts new file mode 100644 index 000000000..80be79bad --- /dev/null +++ b/packages/ui/src/models/camel-component-lookup.ts @@ -0,0 +1,4 @@ +export interface ICamelElementLookupResult { + processorName: string; + componentName?: string; +} diff --git a/packages/ui/src/models/camel/camel-k-resource.ts b/packages/ui/src/models/camel/camel-k-resource.ts index dd41dfef5..483a6073c 100644 --- a/packages/ui/src/models/camel/camel-k-resource.ts +++ b/packages/ui/src/models/camel/camel-k-resource.ts @@ -1,14 +1,15 @@ -import { MetadataEntity } from '../visualization/metadata'; import { Integration as IntegrationType, - Kamelet as KameletType, KameletBinding as KameletBindingType, + Kamelet as KameletType, Pipe as PipeType, } from '@kaoto-next/camel-catalog/types'; +import { ComponentsCatalogTypes } from '..'; +import { BaseVisualCamelEntity } from '../visualization/base-visual-entity'; +import { MetadataEntity } from '../visualization/metadata'; import { CamelResource } from './camel-resource'; import { BaseCamelEntity } from './entities'; import { SourceSchemaType } from './source-schema-type'; -import { BaseVisualCamelEntity } from '../visualization/base-visual-entity'; export type CamelKType = IntegrationType | KameletType | KameletBindingType | PipeType; @@ -61,6 +62,10 @@ export abstract class CamelKResource implements CamelResource { return answer; } + getDefaultNodeDefition(_newComponent: ComponentsCatalogTypes | undefined): object { + throw new Error('Method not implemented.'); + } + abstract getType(): SourceSchemaType; abstract getVisualEntities(): BaseVisualCamelEntity[]; diff --git a/packages/ui/src/models/camel/camel-resource.ts b/packages/ui/src/models/camel/camel-resource.ts index e1e6f4acd..8646eee88 100644 --- a/packages/ui/src/models/camel/camel-resource.ts +++ b/packages/ui/src/models/camel/camel-resource.ts @@ -1,18 +1,19 @@ +import { + Integration as IntegrationType, + KameletBinding as KameletBindingType, + Kamelet as KameletType, + Pipe as PipeType, +} from '@kaoto-next/camel-catalog/types'; +import { ComponentsCatalogTypes } from '../camel-catalog-index'; import { BaseVisualCamelEntity } from '../visualization/base-visual-entity'; -import { BaseCamelEntity } from './entities'; +import { BeansEntity } from '../visualization/metadata'; import { CamelRouteResource } from './camel-route-resource'; +import { BaseCamelEntity } from './entities'; import { IntegrationResource } from './integration-resource'; -import { KameletResource } from './kamelet-resource'; import { KameletBindingResource } from './kamelet-binding-resource'; +import { KameletResource } from './kamelet-resource'; import { PipeResource } from './pipe-resource'; import { SourceSchemaType } from './source-schema-type'; -import { BeansEntity } from '../visualization/metadata'; -import { - Integration as IntegrationType, - Kamelet as KameletType, - KameletBinding as KameletBindingType, - Pipe as PipeType, -} from '@kaoto-next/camel-catalog/types'; export interface CamelResource { getVisualEntities(): BaseVisualCamelEntity[]; @@ -22,6 +23,9 @@ export interface CamelResource { supportsMultipleVisualEntities(): boolean; toJSON(): unknown; getType(): SourceSchemaType; + + /** Components Catalog related methods */ + getDefaultNodeDefition(newComponent: ComponentsCatalogTypes | undefined): object; } export interface BeansAwareResource { diff --git a/packages/ui/src/models/camel/camel-route-resource.ts b/packages/ui/src/models/camel/camel-route-resource.ts index c40db69a7..523a5aa8e 100644 --- a/packages/ui/src/models/camel/camel-route-resource.ts +++ b/packages/ui/src/models/camel/camel-route-resource.ts @@ -1,11 +1,13 @@ -import { BeansAwareResource, CamelResource } from './camel-resource'; -import { BaseCamelEntity } from './entities'; +import { RouteDefinition } from '@kaoto-next/camel-catalog/types'; +import { isDefined } from '../../utils'; +import { ComponentsCatalogTypes } from '../camel-catalog-index'; import { CamelRouteVisualEntity, isCamelRoute } from '../visualization/flows'; +import { flowTemplateService } from '../visualization/flows/flow-templates-service'; +import { CamelComponentDefaultService } from '../visualization/flows/support/camel-component-default.service'; import { BeansEntity, isBeans } from '../visualization/metadata'; +import { BeansAwareResource, CamelResource } from './camel-resource'; +import { BaseCamelEntity } from './entities'; import { SourceSchemaType } from './source-schema-type'; -import { isDefined } from '../../utils'; -import { flowTemplateService } from '../visualization/flows/flow-templates-service'; -import { RouteDefinition } from '@kaoto-next/camel-catalog/types'; export class CamelRouteResource implements CamelResource, BeansAwareResource { private entities: BaseCamelEntity[] = []; @@ -88,4 +90,9 @@ export class CamelRouteResource implements CamelResource, BeansAwareResource { this.addNewEntity(); } } + + /** Components Catalog related methods */ + getDefaultNodeDefition(newComponent: ComponentsCatalogTypes): object { + return CamelComponentDefaultService.getDefaultValue(newComponent); + } } diff --git a/packages/ui/src/models/visualization/base-visual-entity.ts b/packages/ui/src/models/visualization/base-visual-entity.ts index 6407cf8fd..0dd4a8dc9 100644 --- a/packages/ui/src/models/visualization/base-visual-entity.ts +++ b/packages/ui/src/models/visualization/base-visual-entity.ts @@ -23,6 +23,9 @@ export interface BaseVisualCamelEntity extends BaseCamelEntity { /** Retrieve the steps from the underlying Camel entity */ getSteps: () => unknown[]; + /** Add a step to the underlying Camel entity */ + addStep: (options: { definition: object; mode: 'prepend' | 'append'; data: IVisualizationNodeData }) => void; + /** Remove the step at a given path from the underlying Camel entity */ removeStep: (path?: string) => void; @@ -45,6 +48,10 @@ export interface IVisualizationNode { diff --git a/packages/ui/src/models/visualization/flows/camel-route-visual-entity.ts b/packages/ui/src/models/visualization/flows/camel-route-visual-entity.ts index c9f94ea0b..a2e80e92b 100644 --- a/packages/ui/src/models/visualization/flows/camel-route-visual-entity.ts +++ b/packages/ui/src/models/visualization/flows/camel-route-visual-entity.ts @@ -5,6 +5,7 @@ import set from 'lodash.set'; import { getCamelRandomId } from '../../../camel-utils/camel-random-id'; import { isDefined } from '../../../utils'; import { NodeIconResolver } from '../../../utils/node-icon-resolver'; +import { ICamelElementLookupResult } from '../../camel-component-lookup'; import { EntityType } from '../../camel/entities'; import { BaseVisualCamelEntity, @@ -13,7 +14,7 @@ import { VisualComponentSchema, } from '../base-visual-entity'; import { createVisualizationNode } from '../visualization-node'; -import { CamelComponentSchemaService, ICamelElementLookupResult } from './camel-component-schema.service'; +import { CamelComponentSchemaService } from './support/camel-component-schema.service'; type CamelRouteVisualEntityData = IVisualizationNodeData & ICamelElementLookupResult; @@ -70,6 +71,73 @@ export class CamelRouteVisualEntity implements BaseVisualCamelEntity { return this.route.from?.steps ?? []; } + /** + * Add a step to the route + * + * path examples: + * from + * from.steps.0.setHeader + * from.steps.1.choice.otherwise + * from.steps.1.choice.otherwise.steps.0.setHeader + * from.steps.1.choice.when.0 + * from.steps.1.choice.when.0.steps.0.setHeader + */ + addStep(options: { + definition: ProcessorDefinition; + mode: 'prepend' | 'append'; + data: IVisualizationNodeData; + }): void { + if (options.data.path === undefined) return; + + console.log(options); + + const stepsProperties = CamelComponentSchemaService.getProcessorStepsProperties( + (options.data as CamelRouteVisualEntityData).processorName, + ); + if (stepsProperties.length === 1 && stepsProperties[0].type === 'list') { + const arrayPath = `${options.data.path}.${stepsProperties[0].name}`; + let stepsArray: ProcessorDefinition[] | undefined = get(this.route, arrayPath); + if (!Array.isArray(stepsArray)) { + set(this.route, arrayPath, []); + stepsArray = get(this.route, arrayPath) as ProcessorDefinition[]; + } + + stepsArray.unshift(options.definition); + + return; + } + + /** + * If there's only one path segment, it means the target is the `from` property of the route + * therefore we insert the step as the first element in the array + */ + if (options.data.path === 'from') { + this.route.from?.steps.unshift(options.definition); + return; + } + + const pathArray = options.data.path.split('.'); + const last = pathArray[pathArray.length - 1]; + const penultimate = pathArray[pathArray.length - 2]; + + /** + * If the last segment is a string and the penultimate is a number, it means the target is member of an array + * therefore we need to look for the array and insert the element at the given index + 1 + * + * f.i. from.steps.0.setHeader + * penultimate: 0 + * last: setHeader + */ + if (!Number.isInteger(Number(last)) && Number.isInteger(Number(penultimate))) { + const desiredIndex = options.mode === 'prepend' ? Number(penultimate) : Number(penultimate) + 1; + + const stepsArray: ProcessorDefinition[] = get(this.route, pathArray.slice(0, -2), []); + stepsArray.splice(desiredIndex, 0, options.definition); + + return; + } + } + removeStep(path?: string): void { if (!path) return; /** @@ -128,7 +196,7 @@ export class CamelRouteVisualEntity implements BaseVisualCamelEntity { } } - toVizNode(): IVisualizationNode { + toVizNode(): IVisualizationNode { const rootNode = this.getVizNodeFromProcessor('from', { processorName: 'from' }); rootNode.data.entity = this; @@ -145,10 +213,7 @@ export class CamelRouteVisualEntity implements BaseVisualCamelEntity { return rootNode; } - private getVizNodeFromProcessor( - path: string, - componentLookup: ICamelElementLookupResult, - ): IVisualizationNode { + private getVizNodeFromProcessor(path: string, componentLookup: ICamelElementLookupResult): IVisualizationNode { const data: CamelRouteVisualEntityData = { label: CamelComponentSchemaService.getLabel(componentLookup, get(this.route, path)), path, diff --git a/packages/ui/src/models/visualization/flows/kamelet-visual-entity.ts b/packages/ui/src/models/visualization/flows/kamelet-visual-entity.ts index c417e0af8..e7cb1a257 100644 --- a/packages/ui/src/models/visualization/flows/kamelet-visual-entity.ts +++ b/packages/ui/src/models/visualization/flows/kamelet-visual-entity.ts @@ -25,6 +25,10 @@ export class KameletVisualEntity implements BaseVisualCamelEntity { return []; // TODO } + addStep(): void { + return; // TODO + } + removeStep(): void { return; // TODO } diff --git a/packages/ui/src/models/visualization/flows/pipe-visual-entity.test.ts b/packages/ui/src/models/visualization/flows/pipe-visual-entity.test.ts index 8aa4a27c6..fe445b7f9 100644 --- a/packages/ui/src/models/visualization/flows/pipe-visual-entity.test.ts +++ b/packages/ui/src/models/visualization/flows/pipe-visual-entity.test.ts @@ -3,8 +3,8 @@ import { JSONSchemaType } from 'ajv'; import cloneDeep from 'lodash/cloneDeep'; import { pipeJson } from '../../../stubs/pipe'; import { EntityType } from '../../camel/entities'; -import { KameletSchemaService } from './kamelet-schema.service'; import { PipeVisualEntity } from './pipe-visual-entity'; +import { KameletSchemaService } from './support/kamelet-schema.service'; describe('Pipe', () => { let pipeCR: Pipe; diff --git a/packages/ui/src/models/visualization/flows/pipe-visual-entity.ts b/packages/ui/src/models/visualization/flows/pipe-visual-entity.ts index 535fce93a..53ca8f128 100644 --- a/packages/ui/src/models/visualization/flows/pipe-visual-entity.ts +++ b/packages/ui/src/models/visualization/flows/pipe-visual-entity.ts @@ -1,7 +1,6 @@ import get from 'lodash.get'; import set from 'lodash.set'; import { v4 as uuidv4 } from 'uuid'; -import { NodeIconResolver } from '../../../utils/node-icon-resolver'; import { EntityType } from '../../camel/entities'; import { PipeSpec, PipeStep, PipeSteps } from '../../camel/entities/pipe-overrides'; import { @@ -11,7 +10,8 @@ import { VisualComponentSchema, } from '../base-visual-entity'; import { createVisualizationNode } from '../visualization-node'; -import { KameletSchemaService } from './kamelet-schema.service'; +import { KameletSchemaService } from './support/kamelet-schema.service'; +import { NodeIconResolver } from '../../../utils/node-icon-resolver'; export class PipeVisualEntity implements BaseVisualCamelEntity { readonly id = uuidv4(); @@ -53,6 +53,10 @@ export class PipeVisualEntity implements BaseVisualCamelEntity { return allSteps; } + addStep(): void { + // TODO + } + removeStep(path?: string): void { /** This method needs to be enabled after passing the entire parent to this class*/ if (!path) return; diff --git a/packages/ui/src/models/visualization/flows/support/camel-component-default.service.ts b/packages/ui/src/models/visualization/flows/support/camel-component-default.service.ts new file mode 100644 index 000000000..538d7faa4 --- /dev/null +++ b/packages/ui/src/models/visualization/flows/support/camel-component-default.service.ts @@ -0,0 +1,73 @@ +import { ProcessorDefinition } from '@kaoto-next/camel-catalog/types'; +import { parse } from 'yaml'; +import { getCamelRandomId } from '../../../../camel-utils/camel-random-id'; +import { ComponentsCatalogTypes } from '../../../camel-catalog-index'; +import { CatalogKind } from '../../../catalog-kind'; +/** + * CamelComponentDefaultService + * + * This class is meant to provide working default values for Camel components. + */ +export class CamelComponentDefaultService { + /** + * Get the default value for a given component and property + */ + static getDefaultValue(componentDefinition: ComponentsCatalogTypes): ProcessorDefinition { + if ('component' in componentDefinition && componentDefinition.component.kind === CatalogKind.Component) { + return this.getDefaultValueFromComponent(componentDefinition.component.name); + } else if ('kind' in componentDefinition && componentDefinition.kind === CatalogKind.Kamelet) { + return this.getDefaultValueFromKamelet(componentDefinition.metadata.name); + } else if ('model' in componentDefinition && componentDefinition.model.kind === CatalogKind.Processor) { + return this.getDefaultValueFromProcessor(componentDefinition.model.name as keyof ProcessorDefinition); + } + + return {}; + } + + private static getDefaultValueFromComponent(componentName: string): object { + switch (componentName) { + default: + return parse(` + to: + uri: "${componentName}" + id: ${getCamelRandomId('to')} + `); + } + } + + private static getDefaultValueFromKamelet(componentName: string): object { + switch (componentName) { + default: + return parse(` + to: + uri: "kamelet:${componentName}" + id: ${getCamelRandomId('to')} + `); + } + } + + private static getDefaultValueFromProcessor(processorName: keyof ProcessorDefinition): ProcessorDefinition { + switch (processorName) { + case 'choice': + return parse(` + choice: + when: + - simple: "\${header.foo} == 1" + steps: + - log: + id: ${getCamelRandomId('log')} + message: "\${body}" + otherwise: + steps: + - log: + id: ${getCamelRandomId('log')} + message: "\${body}" + `); + + default: + return { + [processorName]: {}, + }; + } + } +} diff --git a/packages/ui/src/models/visualization/flows/camel-component-schema.service.ts b/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.ts similarity index 91% rename from packages/ui/src/models/visualization/flows/camel-component-schema.service.ts rename to packages/ui/src/models/visualization/flows/support/camel-component-schema.service.ts index e268270ef..e1e80039a 100644 --- a/packages/ui/src/models/visualization/flows/camel-component-schema.service.ts +++ b/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.ts @@ -1,15 +1,11 @@ import type { JSONSchemaType } from 'ajv'; -import { isDefined } from '../../../utils'; -import { ICamelComponentProperty } from '../../camel-components-catalog'; -import { ICamelProcessorProperty } from '../../camel-processors-catalog'; -import { CatalogKind } from '../../catalog-kind'; -import { VisualComponentSchema } from '../base-visual-entity'; -import { CamelCatalogService } from './camel-catalog.service'; - -export interface ICamelElementLookupResult { - processorName: string; - componentName?: string; -} +import { isDefined } from '../../../../utils'; +import { ICamelComponentProperty } from '../../../camel-components-catalog'; +import { ICamelProcessorProperty } from '../../../camel-processors-catalog'; +import { CatalogKind } from '../../../catalog-kind'; +import { VisualComponentSchema } from '../../base-visual-entity'; +import { CamelCatalogService } from '../camel-catalog.service'; +import { ICamelElementLookupResult } from '../../../camel-component-lookup'; export class CamelComponentSchemaService { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -231,6 +227,19 @@ export class CamelComponentSchemaService { return schema; } + static getCompatibleProcessors(processor: string) { + switch (processor) { + case 'choice': + return ['when', 'otherwise']; + + case 'doTry': + return ['doCatch', 'doFinally']; + + default: + return []; + } + } + /** * Transform Camel property types into JSON Schema types * diff --git a/packages/ui/src/models/visualization/flows/kamelet-schema.service.ts b/packages/ui/src/models/visualization/flows/support/kamelet-schema.service.ts similarity index 83% rename from packages/ui/src/models/visualization/flows/kamelet-schema.service.ts rename to packages/ui/src/models/visualization/flows/support/kamelet-schema.service.ts index 1418f794f..7a2f66ef4 100644 --- a/packages/ui/src/models/visualization/flows/kamelet-schema.service.ts +++ b/packages/ui/src/models/visualization/flows/support/kamelet-schema.service.ts @@ -1,9 +1,9 @@ import { JSONSchemaType } from 'ajv'; -import { KameletBindingStep, PipeStep } from '../../camel/entities'; -import { CatalogKind } from '../../catalog-kind'; -import { IKameletDefinition } from '../../kamelets-catalog'; -import { VisualComponentSchema } from '../base-visual-entity'; -import { CamelCatalogService } from './camel-catalog.service'; +import { KameletBindingStep, PipeStep } from '../../../camel/entities'; +import { CatalogKind } from '../../../catalog-kind'; +import { IKameletDefinition } from '../../../kamelets-catalog'; +import { VisualComponentSchema } from '../../base-visual-entity'; +import { CamelCatalogService } from '../camel-catalog.service'; export class KameletSchemaService { static getVisualComponentSchema(stepModel: PipeStep): VisualComponentSchema | undefined { diff --git a/packages/ui/src/models/visualization/visualization-node.ts b/packages/ui/src/models/visualization/visualization-node.ts index 2006affed..50eb700cd 100644 --- a/packages/ui/src/models/visualization/visualization-node.ts +++ b/packages/ui/src/models/visualization/visualization-node.ts @@ -32,6 +32,14 @@ class VisualizationNode void; + getNewComponent: (options?: Partial) => Promise; } interface CatalogModalProviderProps { - onTileClick?: (tile: ITile) => void; + onTileClick?: (component: ComponentsCatalogTypes) => void; } export const CatalogModalContext = createContext(undefined); @@ -29,18 +46,58 @@ export const CatalogModalContext = createContext> = (props) => { - const [isModalOpen, setIsModalOpen] = useState(false); + const camelCatalogService = useContext(CatalogContext); const tiles = useContext(CatalogTilesContext); + const [filteredTiles, setFilteredTiles] = useState(tiles); + const [isModalOpen, setIsModalOpen] = useState(false); + + const componentSelectionRef = useRef<{ + resolve: (component: ComponentsCatalogTypes | undefined) => void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + reject: (error: any) => any; + }>(); + + const handleCloseModal = useCallback(() => { + setIsModalOpen(false); + componentSelectionRef.current?.resolve(undefined); + }, []); + + const handleSelectComponent = useCallback( + (tile: ITile) => { + setIsModalOpen(false); + const component = camelCatalogService.getComponent(tile.type as CatalogKind, tile.name); - const handleModalToggle = () => { - setIsModalOpen(!isModalOpen); - }; + componentSelectionRef.current?.resolve(component); + }, + [camelCatalogService], + ); + + const getNewComponent = useCallback( + (options: Partial = {}) => { + setIsModalOpen(true); + + if (isDefined(options.filters) && Array.isArray(options.filters)) { + const localFilteredTiles = tiles.filter((tile) => options.filters?.includes(tile.name)); + + setFilteredTiles(localFilteredTiles); + } + + const componentSelectorPromise = new Promise((resolve, reject) => { + /** Set both resolve and reject functions to be used once the component is selected */ + componentSelectionRef.current = { resolve, reject }; + }); + + return componentSelectorPromise; + }, + [tiles], + ); const value: CatalogModalContextValue = useMemo( () => ({ setIsModalOpen, + getNewComponent, }), - [], + [getNewComponent], ); return ( @@ -48,8 +105,8 @@ export const CatalogModalProvider: FunctionComponent - + + )}