You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ma...@apache.org on 2023/10/27 23:44:21 UTC

(camel-karavan) branch main updated: Create Kamelet from Existing #315

This is an automated email from the ASF dual-hosted git repository.

marat pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-karavan.git


The following commit(s) were added to refs/heads/main by this push:
     new 4ce5f9f9 Create Kamelet from Existing #315
4ce5f9f9 is described below

commit 4ce5f9f9e92c4c7b0bbcf0434dc6dc82efb10221
Author: Marat Gubaidullin <ma...@talismancloud.io>
AuthorDate: Fri Oct 27 19:44:10 2023 -0400

    Create Kamelet from Existing #315
---
 .../route/property/KameletPropertyField.tsx        |   1 -
 .../src/designer/ui/TypeaheadSelect.tsx            | 233 +++++++++++++++++++++
 karavan-designer/src/designer/utils/CamelUi.tsx    |   2 +-
 karavan-space/src/designer/KaravanDesigner.tsx     |   2 -
 .../src/designer/icons/ComponentIcons.tsx          |  23 +-
 karavan-space/src/designer/icons/KaravanIcons.tsx  |   3 +-
 .../src/designer/route/DslConnections.tsx          |   2 +
 karavan-space/src/designer/route/DslElement.tsx    |  14 +-
 karavan-space/src/designer/route/DslSelector.tsx   |   3 +-
 karavan-space/src/designer/route/RouteDesigner.tsx |  53 +++--
 .../route/property/KameletPropertyField.tsx        |   1 -
 .../src/designer/route/useRouteDesignerHook.tsx    |  42 +++-
 karavan-space/src/designer/ui/TypeaheadSelect.tsx  | 233 +++++++++++++++++++++
 karavan-space/src/designer/utils/CamelUi.tsx       |  12 +-
 .../main/webui/src/designer/ui/TypeaheadSelect.tsx | 233 +++++++++++++++++++++
 .../src/main/webui/src/designer/utils/CamelUi.tsx  |   2 +-
 .../webui/src/project/files/CreateFileModal.tsx    |  86 ++------
 17 files changed, 844 insertions(+), 101 deletions(-)

diff --git a/karavan-designer/src/designer/route/property/KameletPropertyField.tsx b/karavan-designer/src/designer/route/property/KameletPropertyField.tsx
index 0bac1c32..96b880c1 100644
--- a/karavan-designer/src/designer/route/property/KameletPropertyField.tsx
+++ b/karavan-designer/src/designer/route/property/KameletPropertyField.tsx
@@ -34,7 +34,6 @@ import ShowIcon from "@patternfly/react-icons/dist/js/icons/eye-icon";
 import HideIcon from "@patternfly/react-icons/dist/js/icons/eye-slash-icon";
 import DockerIcon from "@patternfly/react-icons/dist/js/icons/docker-icon";
 import {usePropertiesHook} from "../usePropertiesHook";
-import {CamelUi} from "../../utils/CamelUi";
 import {Select, SelectDirection, SelectOption, SelectVariant} from "@patternfly/react-core/deprecated";
 
 interface Props {
diff --git a/karavan-designer/src/designer/ui/TypeaheadSelect.tsx b/karavan-designer/src/designer/ui/TypeaheadSelect.tsx
new file mode 100644
index 00000000..a529db43
--- /dev/null
+++ b/karavan-designer/src/designer/ui/TypeaheadSelect.tsx
@@ -0,0 +1,233 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import {
+    Select,
+    SelectOption,
+    SelectList,
+    SelectOptionProps,
+    MenuToggle,
+    MenuToggleElement,
+    TextInputGroup,
+    TextInputGroupMain,
+    TextInputGroupUtilities,
+    Button
+} from '@patternfly/react-core';
+import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
+
+export interface Value {
+    value: string
+    children: string
+}
+
+interface Props {
+    listOfValues: Value[]
+    onSelect: (value: string) => void
+}
+
+export function TypeaheadSelect(props: Props)  {
+    const [isOpen, setIsOpen] = React.useState(false);
+    const [selected, setSelected] = React.useState<string>('');
+    const [inputValue, setInputValue] = React.useState<string>('');
+    const [filterValue, setFilterValue] = React.useState<string>('');
+    const [selectOptions, setSelectOptions] = React.useState<SelectOptionProps[]>(props.listOfValues);
+    const [focusedItemIndex, setFocusedItemIndex] = React.useState<number | undefined>(undefined);
+    const [activeItem, setActiveItem] = React.useState<string | undefined>(undefined);
+    const textInputRef = React.useRef<HTMLInputElement>();
+
+    React.useEffect(() => {
+        let newSelectOptions: SelectOptionProps[] = props.listOfValues;
+
+        // Filter menu items based on the text input value when one exists
+        if (filterValue) {
+            newSelectOptions = props.listOfValues.filter((menuItem) =>
+                String(menuItem.children).toLowerCase().includes(filterValue.toLowerCase())
+            );
+
+            // When no options are found after filtering, display 'No results found'
+            if (!newSelectOptions.length) {
+                newSelectOptions = [
+                    { isDisabled: false, children: `No results found for "${filterValue}"`, value: 'no results' }
+                ];
+            }
+
+            // Open the menu when the input value changes and the new value is not empty
+            if (!isOpen) {
+                setIsOpen(true);
+            }
+        }
+
+        setSelectOptions(newSelectOptions);
+        setActiveItem(undefined);
+        setFocusedItemIndex(undefined);
+    }, [filterValue]);
+
+    const onToggleClick = () => {
+        setIsOpen(!isOpen);
+    };
+
+    const onSelect = (_event: React.MouseEvent<Element, MouseEvent> | undefined, value: string | number | undefined) => {
+        if (value) {
+            props.onSelect( value.toString())
+        }
+
+        if (value && value !== 'no results') {
+            setInputValue(value as string);
+            setFilterValue('');
+            const text = props.listOfValues.filter(v => v.value === value).at(0)?.children;
+            setSelected(text || value.toString());
+            setInputValue(text || value.toString());
+        }
+        setIsOpen(false);
+        setFocusedItemIndex(undefined);
+        setActiveItem(undefined);
+    };
+
+    const onTextInputChange = (_event: React.FormEvent<HTMLInputElement>, value: string) => {
+        setInputValue(value);
+        setFilterValue(value);
+    };
+
+    const handleMenuArrowKeys = (key: string) => {
+        let indexToFocus;
+
+        if (isOpen) {
+            if (key === 'ArrowUp') {
+                // When no index is set or at the first index, focus to the last, otherwise decrement focus index
+                if (focusedItemIndex === undefined || focusedItemIndex === 0) {
+                    indexToFocus = selectOptions.length - 1;
+                } else {
+                    indexToFocus = focusedItemIndex - 1;
+                }
+            }
+
+            if (key === 'ArrowDown') {
+                // When no index is set or at the last index, focus to the first, otherwise increment focus index
+                if (focusedItemIndex === undefined || focusedItemIndex === selectOptions.length - 1) {
+                    indexToFocus = 0;
+                } else {
+                    indexToFocus = focusedItemIndex + 1;
+                }
+            }
+
+            if (indexToFocus !== undefined) {
+                setFocusedItemIndex(indexToFocus);
+                const focusedItem = selectOptions[indexToFocus];
+                setActiveItem(`select-typeahead-${focusedItem.value}`);
+            }
+        }
+    };
+
+    const onInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
+        const enabledMenuItems = selectOptions.filter((option) => !option.isDisabled);
+        const [firstMenuItem] = enabledMenuItems;
+        const focusedItem = focusedItemIndex ? enabledMenuItems[focusedItemIndex] : firstMenuItem;
+
+        switch (event.key) {
+            // Select the first available option
+            case 'Enter':
+                if (isOpen && focusedItem.value !== 'no results') {
+                    setInputValue(String(focusedItem.children));
+                    setFilterValue('');
+                    setSelected(String(focusedItem.children));
+                    props.onSelect(focusedItem.value)
+                }
+
+                setIsOpen((prevIsOpen) => !prevIsOpen);
+                setFocusedItemIndex(undefined);
+                setActiveItem(undefined);
+
+                break;
+            case 'Tab':
+            case 'Escape':
+                setIsOpen(false);
+                setActiveItem(undefined);
+                break;
+            case 'ArrowUp':
+            case 'ArrowDown':
+                event.preventDefault();
+                handleMenuArrowKeys(event.key);
+                break;
+        }
+    };
+
+    const toggle = (toggleRef: React.Ref<MenuToggleElement>) => (
+        <MenuToggle ref={toggleRef} variant="typeahead" onClick={onToggleClick} isExpanded={isOpen} isFullWidth>
+            <TextInputGroup isPlain>
+                <TextInputGroupMain
+                    value={inputValue}
+                    onClick={onToggleClick}
+                    onChange={onTextInputChange}
+                    onKeyDown={onInputKeyDown}
+                    id="typeahead-select-input"
+                    autoComplete="off"
+                    innerRef={textInputRef}
+                    placeholder="Select a state"
+                    {...(activeItem && { 'aria-activedescendant': activeItem })}
+                    role="combobox"
+                    isExpanded={isOpen}
+                    aria-controls="select-typeahead-listbox"
+                />
+
+                <TextInputGroupUtilities>
+                    {!!inputValue && (
+                        <Button
+                            variant="plain"
+                            onClick={() => {
+                                setSelected('');
+                                setInputValue('');
+                                setFilterValue('');
+                                textInputRef?.current?.focus();
+                            }}
+                            aria-label="Clear input value"
+                        >
+                            <TimesIcon aria-hidden />
+                        </Button>
+                    )}
+                </TextInputGroupUtilities>
+            </TextInputGroup>
+        </MenuToggle>
+    );
+
+    return (
+        <Select
+            id="typeahead-select"
+            isOpen={isOpen}
+            selected={selected}
+            onSelect={onSelect}
+            onOpenChange={() => {
+                setIsOpen(false);
+            }}
+            toggle={toggle}
+        >
+            <SelectList id="select-typeahead-listbox">
+                {selectOptions.map((option, index) => (
+                    <SelectOption
+                        key={option.value || option.children}
+                        isFocused={focusedItemIndex === index}
+                        className={option.className}
+                        onClick={() => setSelected(option.value)}
+                        id={`select-typeahead-${option.value}`}
+                        {...option}
+                        ref={null}
+                    />
+                ))}
+            </SelectList>
+        </Select>
+    );
+};
diff --git a/karavan-designer/src/designer/utils/CamelUi.tsx b/karavan-designer/src/designer/utils/CamelUi.tsx
index 2b672a8d..ec4d5796 100644
--- a/karavan-designer/src/designer/utils/CamelUi.tsx
+++ b/karavan-designer/src/designer/utils/CamelUi.tsx
@@ -108,7 +108,7 @@ const StepElements: string[] = [
     // "ErrorHandlerDefinition",
     "FilterDefinition",
     "IdempotentConsumerDefinition",
-    "KameletDefinition",
+    // "KameletDefinition",
     "LogDefinition",
     "LoopDefinition",
     "MarshalDefinition",
diff --git a/karavan-space/src/designer/KaravanDesigner.tsx b/karavan-space/src/designer/KaravanDesigner.tsx
index 4b7b3c7a..b1af73c5 100644
--- a/karavan-space/src/designer/KaravanDesigner.tsx
+++ b/karavan-space/src/designer/KaravanDesigner.tsx
@@ -24,8 +24,6 @@ import {
     Tabs,
     TabTitleIcon,
     TabTitleText,
-    Tooltip,
-    TooltipPosition,
 } from '@patternfly/react-core';
 import './karavan.css';
 import {RouteDesigner} from "./route/RouteDesigner";
diff --git a/karavan-space/src/designer/icons/ComponentIcons.tsx b/karavan-space/src/designer/icons/ComponentIcons.tsx
index a05f5e70..bda35d53 100644
--- a/karavan-space/src/designer/icons/ComponentIcons.tsx
+++ b/karavan-space/src/designer/icons/ComponentIcons.tsx
@@ -1165,7 +1165,28 @@ export function ApiIcon() {
     );
 }
 
-export function MonitoringIcon() {
+export function KameletIcon() {
+    return (
+        <svg
+            className="icon" id="icon"
+            xmlns="http://www.w3.org/2000/svg"
+            viewBox="0 0 32 32"
+        >
+            <title>{"application"}</title>
+            <path d="M16 18H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2ZM6 6v10h10V6ZM26 12v4h-4v-4h4m0-2h-4a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2ZM26 22v4h-4v-4h4m0-2h-4a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2ZM16 22v4h-4v-4h4m0-2h-4a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2Z" />
+            <path
+                d="M0 0h32v32H0z"
+                data-name="&lt;Transparent Rectangle&gt;"
+                style={{
+                    fill: "none",
+                }}
+            />
+        </svg>
+    )
+}
+
+
+    export function MonitoringIcon() {
     return (
         <svg
             xmlns="http://www.w3.org/2000/svg"
diff --git a/karavan-space/src/designer/icons/KaravanIcons.tsx b/karavan-space/src/designer/icons/KaravanIcons.tsx
index b7165b93..b0b8042f 100644
--- a/karavan-space/src/designer/icons/KaravanIcons.tsx
+++ b/karavan-space/src/designer/icons/KaravanIcons.tsx
@@ -260,7 +260,7 @@ export function CamelIcon(props?: (JSX.IntrinsicAttributes & React.SVGProps<SVGS
     );
 }
 
-export function getDesignerIcon(icon: string) {
+export function getDesignerIcon(icon: string): React.JSX.Element {
     if (icon === 'kamelet') return (
         <svg
             className="top-icon" id="icon"
@@ -429,6 +429,7 @@ export function getDesignerIcon(icon: string) {
             <rect id="_Transparent_Rectangle_" data-name="&lt;Transparent Rectangle&gt;" className="cls-1" width="32"
                   height="32" transform="translate(0 32) rotate(-90)"/>
         </svg>)
+    return <></>;
 }
 
 
diff --git a/karavan-space/src/designer/route/DslConnections.tsx b/karavan-space/src/designer/route/DslConnections.tsx
index de6a0060..a636339e 100644
--- a/karavan-space/src/designer/route/DslConnections.tsx
+++ b/karavan-space/src/designer/route/DslConnections.tsx
@@ -63,6 +63,7 @@ export function DslConnections() {
             .filter(pos => ["FromDefinition"].includes(pos.step.dslName))
             .filter(pos => !TopologyUtils.isElementInternalComponent(pos.step))
             .filter(pos => !(pos.step.dslName === 'FromDefinition' && TopologyUtils.hasInternalUri(pos.step)))
+            .filter(pos => !(pos.step.dslName === 'FromDefinition' && (pos.step as any).uri === 'kamelet:source'))
             .sort((pos1: DslPosition, pos2: DslPosition) => {
                 const y1 = pos1.headerRect.y + pos1.headerRect.height / 2;
                 const y2 = pos2.headerRect.y + pos2.headerRect.height / 2;
@@ -142,6 +143,7 @@ export function DslConnections() {
             .filter(pos => pos.step.dslName === 'ToDefinition' && !CamelUi.isActionKamelet(pos.step) && !TopologyUtils.isElementInternalComponent(pos.step))
             .filter(pos => !(outgoingDefinitions.includes(pos.step.dslName) && TopologyUtils.hasInternalUri(pos.step)))
             .filter(pos => pos.step.dslName !== 'SagaDefinition')
+            .filter(pos => !CamelUi.isKameletSink(pos.step))
             .sort((pos1: DslPosition, pos2: DslPosition) => {
                 const y1 = pos1.headerRect.y + pos1.headerRect.height / 2;
                 const y2 = pos2.headerRect.y + pos2.headerRect.height / 2;
diff --git a/karavan-space/src/designer/route/DslElement.tsx b/karavan-space/src/designer/route/DslElement.tsx
index 305b7859..9e4a8c70 100644
--- a/karavan-space/src/designer/route/DslElement.tsx
+++ b/karavan-space/src/designer/route/DslElement.tsx
@@ -42,7 +42,7 @@ interface Props {
 export function DslElement(props: Props) {
 
     const headerRef = React.useRef<HTMLDivElement>(null);
-    const {selectElement, moveElement, onShowDeleteConfirmation, openSelector} = useRouteDesignerHook();
+    const {selectElement, moveElement, onShowDeleteConfirmation, openSelector, isKamelet, isSourceKamelet, isActionKamelet} = useRouteDesignerHook();
 
     const [integration] = useIntegrationStore((s) => [s.integration, s.setIntegration], shallow)
 
@@ -241,9 +241,19 @@ export function DslElement(props: Props) {
         )
     }
 
+    function getHeaderText(step: CamelElement): string {
+        if (isKamelet() && step.dslName === 'ToDefinition' && (step as any).uri === 'kamelet:sink') {
+            return "Sink";
+        } else if (isKamelet() && step.dslName === 'FromDefinition' && (step as any).uri === 'kamelet:source') {
+            return "Source";
+        } else {
+            return (step as any).description ? (step as any).description : CamelUi.getElementTitle(props.step);
+        }
+    }
+
     function getHeaderTextWithTooltip(step: CamelElement) {
         const checkRequired = CamelUtil.checkRequired(step);
-        const title = (step as any).description ? (step as any).description : CamelUi.getElementTitle(props.step);
+        const title = getHeaderText(step);
         let className = hasWideChildrenElement() ? "text text-right" : "text text-bottom";
         if (!checkRequired[0]) className = className + " header-text-required";
         if (checkRequired[0]) {
diff --git a/karavan-space/src/designer/route/DslSelector.tsx b/karavan-space/src/designer/route/DslSelector.tsx
index 8f815536..a80525ba 100644
--- a/karavan-space/src/designer/route/DslSelector.tsx
+++ b/karavan-space/src/designer/route/DslSelector.tsx
@@ -24,7 +24,7 @@ import {
 import '../karavan.css';
 import {CamelUi} from "../utils/CamelUi";
 import {DslMetaModel} from "../utils/DslMetaModel";
-import {useDesignerStore, useSelectorStore} from "../DesignerStore";
+import {useDesignerStore, useIntegrationStore, useSelectorStore} from "../DesignerStore";
 import {shallow} from "zustand/shallow";
 import {useRouteDesignerHook} from "./useRouteDesignerHook";
 
@@ -40,7 +40,6 @@ export function DslSelector (props: Props) {
             [s.showSelector, s.showSteps, s.parentId, s.parentDsl, s.selectorTabIndex, s.setShowSelector, s.setSelectorTabIndex,
                 s.selectedPosition, s.selectedLabels, s.addSelectedLabel, s.deleteSelectedLabel], shallow)
 
-
     const [dark] = useDesignerStore((s) => [s.dark], shallow)
 
     const {onDslSelect} = useRouteDesignerHook();
diff --git a/karavan-space/src/designer/route/RouteDesigner.tsx b/karavan-space/src/designer/route/RouteDesigner.tsx
index 215f334b..c57260db 100644
--- a/karavan-space/src/designer/route/RouteDesigner.tsx
+++ b/karavan-space/src/designer/route/RouteDesigner.tsx
@@ -40,7 +40,8 @@ import {DslElementMoveModal} from "./DslElementMoveModal";
 
 export function RouteDesigner() {
 
-    const {openSelector, createRouteConfiguration, onCommand, handleKeyDown, handleKeyUp, unselectElement} = useRouteDesignerHook();
+    const {openSelector, createRouteConfiguration, onCommand, handleKeyDown, handleKeyUp, unselectElement, onDslSelect,
+        isSourceKamelet, isActionKamelet, isKamelet, isSinkKamelet} = useRouteDesignerHook();
 
     const [integration] = useIntegrationStore((state) => [state.integration], shallow)
     const [showDeleteConfirmation, setPosition, width, height, top, left, hideLogDSL, showMoveConfirmation, setShowMoveConfirmation] =
@@ -104,6 +105,37 @@ export function RouteDesigner() {
         )
     }
 
+    function getGraphButtons() {
+        const routes = CamelUi.getRoutes(integration);
+        const showNewRoute = (isKamelet() && routes.length === 0) || !isKamelet();
+        const showNewRouteConfiguration = !isKamelet();
+        return (
+            <div className="add-flow">
+                {showNewRoute && <Button
+                    variant={routes.length === 0 ? "primary" : "secondary"}
+                    icon={<PlusIcon/>}
+                    onClick={e => {
+                        if (isSinkKamelet() || isActionKamelet()) {
+                            const dsl = CamelUi.getDslMetaModel('FromDefinition');
+                            dsl.uri = 'kamelet:source';
+                            onDslSelect(dsl, '', undefined);
+                        } else {
+                            openSelector(undefined, undefined)
+                        }
+                    }}
+                >
+                    Create route
+                </Button>}
+                {showNewRouteConfiguration && <Button
+                    variant="secondary"
+                    icon={<PlusIcon/>}
+                    onClick={e => createRouteConfiguration()}
+                >
+                    Create configuration
+                </Button>}
+            </div>
+        )
+    }
     function getGraph() {
         const routes = CamelUi.getRoutes(integration);
         const routeConfigurations = CamelUi.getRouteConfigurations(integration);
@@ -129,24 +161,7 @@ export function RouteDesigner() {
                                     step={route}
                                     parent={undefined}/>
                     ))}
-                    <div className="add-flow">
-                        <Button
-                            variant={routes.length === 0 ? "primary" : "secondary"}
-                            icon={<PlusIcon/>}
-                            onClick={e => {
-                                openSelector(undefined, undefined)
-                            }}
-                        >
-                            Create route
-                        </Button>
-                        <Button
-                            variant="secondary"
-                            icon={<PlusIcon/>}
-                            onClick={e => createRouteConfiguration()}
-                        >
-                            Create configuration
-                        </Button>
-                    </div>
+                    {getGraphButtons()}
                 </div>
             </div>)
     }
diff --git a/karavan-space/src/designer/route/property/KameletPropertyField.tsx b/karavan-space/src/designer/route/property/KameletPropertyField.tsx
index 0bac1c32..96b880c1 100644
--- a/karavan-space/src/designer/route/property/KameletPropertyField.tsx
+++ b/karavan-space/src/designer/route/property/KameletPropertyField.tsx
@@ -34,7 +34,6 @@ import ShowIcon from "@patternfly/react-icons/dist/js/icons/eye-icon";
 import HideIcon from "@patternfly/react-icons/dist/js/icons/eye-slash-icon";
 import DockerIcon from "@patternfly/react-icons/dist/js/icons/docker-icon";
 import {usePropertiesHook} from "../usePropertiesHook";
-import {CamelUi} from "../../utils/CamelUi";
 import {Select, SelectDirection, SelectOption, SelectVariant} from "@patternfly/react-core/deprecated";
 
 interface Props {
diff --git a/karavan-space/src/designer/route/useRouteDesignerHook.tsx b/karavan-space/src/designer/route/useRouteDesignerHook.tsx
index fe61d410..f48fc850 100644
--- a/karavan-space/src/designer/route/useRouteDesignerHook.tsx
+++ b/karavan-space/src/designer/route/useRouteDesignerHook.tsx
@@ -19,7 +19,7 @@ import '../karavan.css';
 import {DslMetaModel} from "../utils/DslMetaModel";
 import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
 import {ChoiceDefinition, FromDefinition, LogDefinition, RouteConfigurationDefinition, RouteDefinition} from "karavan-core/lib/model/CamelDefinition";
-import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
+import {CamelElement, MetadataLabels} from "karavan-core/lib/model/IntegrationDefinition";
 import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
 import {CamelDefinitionApi} from "karavan-core/lib/api/CamelDefinitionApi";
 import {Command, EventBus} from "../utils/EventBus";
@@ -27,6 +27,7 @@ import {CamelDisplayUtil} from "karavan-core/lib/api/CamelDisplayUtil";
 import {toPng} from 'html-to-image';
 import {useDesignerStore, useIntegrationStore, useSelectorStore} from "../DesignerStore";
 import {shallow} from "zustand/shallow";
+import {v4 as uuidv4} from 'uuid';
 
 export function useRouteDesignerHook () {
 
@@ -46,6 +47,34 @@ export function useRouteDesignerHook () {
         }
     }
 
+    function isKamelet(): boolean {
+        return integration.type === 'kamelet';
+    }
+
+    function isSourceKamelet(): boolean {
+        if (isKamelet()){
+            const m: MetadataLabels | undefined = integration.metadata.labels;
+            return m !== undefined && m["camel.apache.org/kamelet.type"] === 'source';
+        }
+        return false;
+    }
+
+    function isSinkKamelet(): boolean {
+        if (isKamelet()){
+            const m: MetadataLabels | undefined = integration.metadata.labels;
+            return m !== undefined && m["camel.apache.org/kamelet.type"] === 'sink';
+        }
+        return false;
+    }
+
+    function isActionKamelet(): boolean {
+        if (isKamelet()){
+            const m: MetadataLabels | undefined = integration.metadata.labels;
+            return m !== undefined && m["camel.apache.org/kamelet.type"] === 'action';
+        }
+        return false;
+    }
+
     const onShowDeleteConfirmation = (id: string) => {
         let message: string;
         const uuidsToDelete:string [] = [id];
@@ -193,13 +222,17 @@ export function useRouteDesignerHook () {
         setSelectorTabIndex((parentId === undefined && parentDsl === undefined) ? 'kamelet' : 'eip');
     }
 
-    const onDslSelect = (dsl: DslMetaModel, parentId: string, position?: number | undefined) => {
+    function onDslSelect (dsl: DslMetaModel, parentId: string, position?: number | undefined) {
         switch (dsl.dsl) {
             case 'FromDefinition' :
-                const route = CamelDefinitionApi.createRouteDefinition({from: new FromDefinition({uri: dsl.uri})});
+                const nodePrefixId = isKamelet() ? integration.metadata.name : 'route-' + uuidv4().substring(0,3);
+                const route = CamelDefinitionApi.createRouteDefinition({from: new FromDefinition({uri: dsl.uri}), nodePrefixId: nodePrefixId});
                 addStep(route, parentId, position)
                 break;
             case 'ToDefinition' :
+                if (dsl.uri === undefined && isKamelet()) {
+                    dsl.uri = 'kamelet:sink';
+                }
                 const to = CamelDefinitionApi.createStep(dsl.dsl, {uri: dsl.uri});
                 addStep(to, parentId, position)
                 break;
@@ -291,5 +324,6 @@ export function useRouteDesignerHook () {
     }
 
     return { deleteElement, selectElement, moveElement, onShowDeleteConfirmation, onDslSelect, openSelector,
-        createRouteConfiguration, onCommand, handleKeyDown, handleKeyUp, unselectElement}
+        createRouteConfiguration, onCommand, handleKeyDown, handleKeyUp, unselectElement, isKamelet, isSourceKamelet,
+        isActionKamelet, isSinkKamelet}
 }
\ No newline at end of file
diff --git a/karavan-space/src/designer/ui/TypeaheadSelect.tsx b/karavan-space/src/designer/ui/TypeaheadSelect.tsx
new file mode 100644
index 00000000..a529db43
--- /dev/null
+++ b/karavan-space/src/designer/ui/TypeaheadSelect.tsx
@@ -0,0 +1,233 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import {
+    Select,
+    SelectOption,
+    SelectList,
+    SelectOptionProps,
+    MenuToggle,
+    MenuToggleElement,
+    TextInputGroup,
+    TextInputGroupMain,
+    TextInputGroupUtilities,
+    Button
+} from '@patternfly/react-core';
+import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
+
+export interface Value {
+    value: string
+    children: string
+}
+
+interface Props {
+    listOfValues: Value[]
+    onSelect: (value: string) => void
+}
+
+export function TypeaheadSelect(props: Props)  {
+    const [isOpen, setIsOpen] = React.useState(false);
+    const [selected, setSelected] = React.useState<string>('');
+    const [inputValue, setInputValue] = React.useState<string>('');
+    const [filterValue, setFilterValue] = React.useState<string>('');
+    const [selectOptions, setSelectOptions] = React.useState<SelectOptionProps[]>(props.listOfValues);
+    const [focusedItemIndex, setFocusedItemIndex] = React.useState<number | undefined>(undefined);
+    const [activeItem, setActiveItem] = React.useState<string | undefined>(undefined);
+    const textInputRef = React.useRef<HTMLInputElement>();
+
+    React.useEffect(() => {
+        let newSelectOptions: SelectOptionProps[] = props.listOfValues;
+
+        // Filter menu items based on the text input value when one exists
+        if (filterValue) {
+            newSelectOptions = props.listOfValues.filter((menuItem) =>
+                String(menuItem.children).toLowerCase().includes(filterValue.toLowerCase())
+            );
+
+            // When no options are found after filtering, display 'No results found'
+            if (!newSelectOptions.length) {
+                newSelectOptions = [
+                    { isDisabled: false, children: `No results found for "${filterValue}"`, value: 'no results' }
+                ];
+            }
+
+            // Open the menu when the input value changes and the new value is not empty
+            if (!isOpen) {
+                setIsOpen(true);
+            }
+        }
+
+        setSelectOptions(newSelectOptions);
+        setActiveItem(undefined);
+        setFocusedItemIndex(undefined);
+    }, [filterValue]);
+
+    const onToggleClick = () => {
+        setIsOpen(!isOpen);
+    };
+
+    const onSelect = (_event: React.MouseEvent<Element, MouseEvent> | undefined, value: string | number | undefined) => {
+        if (value) {
+            props.onSelect( value.toString())
+        }
+
+        if (value && value !== 'no results') {
+            setInputValue(value as string);
+            setFilterValue('');
+            const text = props.listOfValues.filter(v => v.value === value).at(0)?.children;
+            setSelected(text || value.toString());
+            setInputValue(text || value.toString());
+        }
+        setIsOpen(false);
+        setFocusedItemIndex(undefined);
+        setActiveItem(undefined);
+    };
+
+    const onTextInputChange = (_event: React.FormEvent<HTMLInputElement>, value: string) => {
+        setInputValue(value);
+        setFilterValue(value);
+    };
+
+    const handleMenuArrowKeys = (key: string) => {
+        let indexToFocus;
+
+        if (isOpen) {
+            if (key === 'ArrowUp') {
+                // When no index is set or at the first index, focus to the last, otherwise decrement focus index
+                if (focusedItemIndex === undefined || focusedItemIndex === 0) {
+                    indexToFocus = selectOptions.length - 1;
+                } else {
+                    indexToFocus = focusedItemIndex - 1;
+                }
+            }
+
+            if (key === 'ArrowDown') {
+                // When no index is set or at the last index, focus to the first, otherwise increment focus index
+                if (focusedItemIndex === undefined || focusedItemIndex === selectOptions.length - 1) {
+                    indexToFocus = 0;
+                } else {
+                    indexToFocus = focusedItemIndex + 1;
+                }
+            }
+
+            if (indexToFocus !== undefined) {
+                setFocusedItemIndex(indexToFocus);
+                const focusedItem = selectOptions[indexToFocus];
+                setActiveItem(`select-typeahead-${focusedItem.value}`);
+            }
+        }
+    };
+
+    const onInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
+        const enabledMenuItems = selectOptions.filter((option) => !option.isDisabled);
+        const [firstMenuItem] = enabledMenuItems;
+        const focusedItem = focusedItemIndex ? enabledMenuItems[focusedItemIndex] : firstMenuItem;
+
+        switch (event.key) {
+            // Select the first available option
+            case 'Enter':
+                if (isOpen && focusedItem.value !== 'no results') {
+                    setInputValue(String(focusedItem.children));
+                    setFilterValue('');
+                    setSelected(String(focusedItem.children));
+                    props.onSelect(focusedItem.value)
+                }
+
+                setIsOpen((prevIsOpen) => !prevIsOpen);
+                setFocusedItemIndex(undefined);
+                setActiveItem(undefined);
+
+                break;
+            case 'Tab':
+            case 'Escape':
+                setIsOpen(false);
+                setActiveItem(undefined);
+                break;
+            case 'ArrowUp':
+            case 'ArrowDown':
+                event.preventDefault();
+                handleMenuArrowKeys(event.key);
+                break;
+        }
+    };
+
+    const toggle = (toggleRef: React.Ref<MenuToggleElement>) => (
+        <MenuToggle ref={toggleRef} variant="typeahead" onClick={onToggleClick} isExpanded={isOpen} isFullWidth>
+            <TextInputGroup isPlain>
+                <TextInputGroupMain
+                    value={inputValue}
+                    onClick={onToggleClick}
+                    onChange={onTextInputChange}
+                    onKeyDown={onInputKeyDown}
+                    id="typeahead-select-input"
+                    autoComplete="off"
+                    innerRef={textInputRef}
+                    placeholder="Select a state"
+                    {...(activeItem && { 'aria-activedescendant': activeItem })}
+                    role="combobox"
+                    isExpanded={isOpen}
+                    aria-controls="select-typeahead-listbox"
+                />
+
+                <TextInputGroupUtilities>
+                    {!!inputValue && (
+                        <Button
+                            variant="plain"
+                            onClick={() => {
+                                setSelected('');
+                                setInputValue('');
+                                setFilterValue('');
+                                textInputRef?.current?.focus();
+                            }}
+                            aria-label="Clear input value"
+                        >
+                            <TimesIcon aria-hidden />
+                        </Button>
+                    )}
+                </TextInputGroupUtilities>
+            </TextInputGroup>
+        </MenuToggle>
+    );
+
+    return (
+        <Select
+            id="typeahead-select"
+            isOpen={isOpen}
+            selected={selected}
+            onSelect={onSelect}
+            onOpenChange={() => {
+                setIsOpen(false);
+            }}
+            toggle={toggle}
+        >
+            <SelectList id="select-typeahead-listbox">
+                {selectOptions.map((option, index) => (
+                    <SelectOption
+                        key={option.value || option.children}
+                        isFocused={focusedItemIndex === index}
+                        className={option.className}
+                        onClick={() => setSelected(option.value)}
+                        id={`select-typeahead-${option.value}`}
+                        {...option}
+                        ref={null}
+                    />
+                ))}
+            </SelectList>
+        </Select>
+    );
+};
diff --git a/karavan-space/src/designer/utils/CamelUi.tsx b/karavan-space/src/designer/utils/CamelUi.tsx
index 6864361b..ec4d5796 100644
--- a/karavan-space/src/designer/utils/CamelUi.tsx
+++ b/karavan-space/src/designer/utils/CamelUi.tsx
@@ -51,7 +51,7 @@ import {
     IgniteIcon,
     InfinispanIcon,
     IotIcon,
-    KafkaIcon,
+    KafkaIcon, KameletIcon,
     KubernetesIcon,
     MachineLearningIcon,
     MailIcon,
@@ -93,6 +93,7 @@ import {
 import React from "react";
 import {TopologyUtils} from "karavan-core/lib/api/TopologyUtils";
 import {CamelDisplayUtil} from "karavan-core/lib/api/CamelDisplayUtil";
+import {getDesignerIcon} from "../icons/KaravanIcons";
 
 const StepElements: string[] = [
     "AggregateDefinition",
@@ -107,6 +108,7 @@ const StepElements: string[] = [
     // "ErrorHandlerDefinition",
     "FilterDefinition",
     "IdempotentConsumerDefinition",
+    // "KameletDefinition",
     "LogDefinition",
     "LoopDefinition",
     "MarshalDefinition",
@@ -306,6 +308,10 @@ export class CamelUi {
         else return false;
     }
 
+    static isKameletSink = (element: CamelElement): boolean => {
+        return element.dslName === 'ToDefinition' && (element as any).uri === 'kamelet:sink';
+    }
+
     static getInternalRouteUris = (integration: Integration, componentName: string, showComponentName: boolean = true): string[] => {
         const result: string[] = [];
         integration.spec.flows?.filter(f => f.dslName === 'RouteDefinition')
@@ -519,7 +525,7 @@ export class CamelUi {
         }
     }
 
-    static getIconForDsl = (dsl: DslMetaModel): JSX.Element => {
+    static getIconForDsl = (dsl: DslMetaModel): React.JSX.Element => {
         if (dsl.dsl && (dsl.dsl === "KameletDefinition" || dsl.navigation === 'kamelet')) {
             return this.getIconFromSource(CamelUi.getKameletIconByName(dsl.name));
         } else if ((dsl.dsl && dsl.dsl === "FromDefinition")
@@ -687,6 +693,8 @@ export class CamelUi {
                 return <ApiIcon/>;
             case 'HeadDefinition' :
                 return <ApiIcon/>;
+            case 'KameletDefinition' :
+                return <KameletIcon/>;
             default:
                 return this.getIconFromSource(CamelUi.getIconSrcForName(dslName))
         }
diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/ui/TypeaheadSelect.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/ui/TypeaheadSelect.tsx
new file mode 100644
index 00000000..a529db43
--- /dev/null
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/ui/TypeaheadSelect.tsx
@@ -0,0 +1,233 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import {
+    Select,
+    SelectOption,
+    SelectList,
+    SelectOptionProps,
+    MenuToggle,
+    MenuToggleElement,
+    TextInputGroup,
+    TextInputGroupMain,
+    TextInputGroupUtilities,
+    Button
+} from '@patternfly/react-core';
+import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
+
+export interface Value {
+    value: string
+    children: string
+}
+
+interface Props {
+    listOfValues: Value[]
+    onSelect: (value: string) => void
+}
+
+export function TypeaheadSelect(props: Props)  {
+    const [isOpen, setIsOpen] = React.useState(false);
+    const [selected, setSelected] = React.useState<string>('');
+    const [inputValue, setInputValue] = React.useState<string>('');
+    const [filterValue, setFilterValue] = React.useState<string>('');
+    const [selectOptions, setSelectOptions] = React.useState<SelectOptionProps[]>(props.listOfValues);
+    const [focusedItemIndex, setFocusedItemIndex] = React.useState<number | undefined>(undefined);
+    const [activeItem, setActiveItem] = React.useState<string | undefined>(undefined);
+    const textInputRef = React.useRef<HTMLInputElement>();
+
+    React.useEffect(() => {
+        let newSelectOptions: SelectOptionProps[] = props.listOfValues;
+
+        // Filter menu items based on the text input value when one exists
+        if (filterValue) {
+            newSelectOptions = props.listOfValues.filter((menuItem) =>
+                String(menuItem.children).toLowerCase().includes(filterValue.toLowerCase())
+            );
+
+            // When no options are found after filtering, display 'No results found'
+            if (!newSelectOptions.length) {
+                newSelectOptions = [
+                    { isDisabled: false, children: `No results found for "${filterValue}"`, value: 'no results' }
+                ];
+            }
+
+            // Open the menu when the input value changes and the new value is not empty
+            if (!isOpen) {
+                setIsOpen(true);
+            }
+        }
+
+        setSelectOptions(newSelectOptions);
+        setActiveItem(undefined);
+        setFocusedItemIndex(undefined);
+    }, [filterValue]);
+
+    const onToggleClick = () => {
+        setIsOpen(!isOpen);
+    };
+
+    const onSelect = (_event: React.MouseEvent<Element, MouseEvent> | undefined, value: string | number | undefined) => {
+        if (value) {
+            props.onSelect( value.toString())
+        }
+
+        if (value && value !== 'no results') {
+            setInputValue(value as string);
+            setFilterValue('');
+            const text = props.listOfValues.filter(v => v.value === value).at(0)?.children;
+            setSelected(text || value.toString());
+            setInputValue(text || value.toString());
+        }
+        setIsOpen(false);
+        setFocusedItemIndex(undefined);
+        setActiveItem(undefined);
+    };
+
+    const onTextInputChange = (_event: React.FormEvent<HTMLInputElement>, value: string) => {
+        setInputValue(value);
+        setFilterValue(value);
+    };
+
+    const handleMenuArrowKeys = (key: string) => {
+        let indexToFocus;
+
+        if (isOpen) {
+            if (key === 'ArrowUp') {
+                // When no index is set or at the first index, focus to the last, otherwise decrement focus index
+                if (focusedItemIndex === undefined || focusedItemIndex === 0) {
+                    indexToFocus = selectOptions.length - 1;
+                } else {
+                    indexToFocus = focusedItemIndex - 1;
+                }
+            }
+
+            if (key === 'ArrowDown') {
+                // When no index is set or at the last index, focus to the first, otherwise increment focus index
+                if (focusedItemIndex === undefined || focusedItemIndex === selectOptions.length - 1) {
+                    indexToFocus = 0;
+                } else {
+                    indexToFocus = focusedItemIndex + 1;
+                }
+            }
+
+            if (indexToFocus !== undefined) {
+                setFocusedItemIndex(indexToFocus);
+                const focusedItem = selectOptions[indexToFocus];
+                setActiveItem(`select-typeahead-${focusedItem.value}`);
+            }
+        }
+    };
+
+    const onInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
+        const enabledMenuItems = selectOptions.filter((option) => !option.isDisabled);
+        const [firstMenuItem] = enabledMenuItems;
+        const focusedItem = focusedItemIndex ? enabledMenuItems[focusedItemIndex] : firstMenuItem;
+
+        switch (event.key) {
+            // Select the first available option
+            case 'Enter':
+                if (isOpen && focusedItem.value !== 'no results') {
+                    setInputValue(String(focusedItem.children));
+                    setFilterValue('');
+                    setSelected(String(focusedItem.children));
+                    props.onSelect(focusedItem.value)
+                }
+
+                setIsOpen((prevIsOpen) => !prevIsOpen);
+                setFocusedItemIndex(undefined);
+                setActiveItem(undefined);
+
+                break;
+            case 'Tab':
+            case 'Escape':
+                setIsOpen(false);
+                setActiveItem(undefined);
+                break;
+            case 'ArrowUp':
+            case 'ArrowDown':
+                event.preventDefault();
+                handleMenuArrowKeys(event.key);
+                break;
+        }
+    };
+
+    const toggle = (toggleRef: React.Ref<MenuToggleElement>) => (
+        <MenuToggle ref={toggleRef} variant="typeahead" onClick={onToggleClick} isExpanded={isOpen} isFullWidth>
+            <TextInputGroup isPlain>
+                <TextInputGroupMain
+                    value={inputValue}
+                    onClick={onToggleClick}
+                    onChange={onTextInputChange}
+                    onKeyDown={onInputKeyDown}
+                    id="typeahead-select-input"
+                    autoComplete="off"
+                    innerRef={textInputRef}
+                    placeholder="Select a state"
+                    {...(activeItem && { 'aria-activedescendant': activeItem })}
+                    role="combobox"
+                    isExpanded={isOpen}
+                    aria-controls="select-typeahead-listbox"
+                />
+
+                <TextInputGroupUtilities>
+                    {!!inputValue && (
+                        <Button
+                            variant="plain"
+                            onClick={() => {
+                                setSelected('');
+                                setInputValue('');
+                                setFilterValue('');
+                                textInputRef?.current?.focus();
+                            }}
+                            aria-label="Clear input value"
+                        >
+                            <TimesIcon aria-hidden />
+                        </Button>
+                    )}
+                </TextInputGroupUtilities>
+            </TextInputGroup>
+        </MenuToggle>
+    );
+
+    return (
+        <Select
+            id="typeahead-select"
+            isOpen={isOpen}
+            selected={selected}
+            onSelect={onSelect}
+            onOpenChange={() => {
+                setIsOpen(false);
+            }}
+            toggle={toggle}
+        >
+            <SelectList id="select-typeahead-listbox">
+                {selectOptions.map((option, index) => (
+                    <SelectOption
+                        key={option.value || option.children}
+                        isFocused={focusedItemIndex === index}
+                        className={option.className}
+                        onClick={() => setSelected(option.value)}
+                        id={`select-typeahead-${option.value}`}
+                        {...option}
+                        ref={null}
+                    />
+                ))}
+            </SelectList>
+        </Select>
+    );
+};
diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx
index 2b672a8d..ec4d5796 100644
--- a/karavan-web/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx
@@ -108,7 +108,7 @@ const StepElements: string[] = [
     // "ErrorHandlerDefinition",
     "FilterDefinition",
     "IdempotentConsumerDefinition",
-    "KameletDefinition",
+    // "KameletDefinition",
     "LogDefinition",
     "LoopDefinition",
     "MarshalDefinition",
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/files/CreateFileModal.tsx b/karavan-web/karavan-app/src/main/webui/src/project/files/CreateFileModal.tsx
index 53473734..688c37e9 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/files/CreateFileModal.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/files/CreateFileModal.tsx
@@ -22,9 +22,7 @@ import {
     FormGroup,
     ModalVariant,
     Form,
-    ToggleGroupItem, ToggleGroup, FormHelperText, HelperText, HelperTextItem, TextInput, Select,
-    SelectOption,
-    SelectList, MenuToggleElement, MenuToggle, TextInputGroup, TextInputGroupMain, TextInputGroupUtilities,
+    ToggleGroupItem, ToggleGroup, FormHelperText, HelperText, HelperTextItem, TextInput,
 } from '@patternfly/react-core';
 import '../../designer/karavan.css';
 import {Integration, KameletTypes, MetadataLabels} from "karavan-core/lib/model/IntegrationDefinition";
@@ -36,6 +34,7 @@ import {ProjectService} from "../../api/ProjectService";
 import {shallow} from "zustand/shallow";
 import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
 import {KameletApi} from "karavan-core/lib/api/KameletApi";
+import {TypeaheadSelect, Value} from "../../designer/ui/TypeaheadSelect";
 
 interface Props {
     types: string[],
@@ -49,9 +48,7 @@ export function CreateFileModal(props: Props) {
     const [name, setName] = useState<string>('');
     const [fileType, setFileType] = useState<string>();
     const [kameletType, setKameletType] = useState<KameletTypes>('source');
-    const [inputValue, setInputValue] = useState<string>();
-    const [selectIsOpen, setSelectIsOpen] = useState<boolean>(false);
-    const [selectedKamelet, setSelectedKamelet] = useState<[string, string]>(['', '']);
+    const [selectedKamelet, setSelectedKamelet] = useState<string>();
 
     useEffect(() => {
         if (props.types.length > 0) {
@@ -73,11 +70,20 @@ export function CreateFileModal(props: Props) {
         if (fileType === 'INTEGRATION') {
             return CamelDefinitionYaml.integrationToYaml(Integration.createNew(name, 'plain'));
         } else if (fileType === 'KAMELET') {
-            console.log(selectedKamelet, inputValue);
             const kameletName = name + (isKamelet ? '-' + kameletType : '');
             const integration = Integration.createNew(kameletName, 'kamelet');
             const meta: MetadataLabels = new MetadataLabels({"camel.apache.org/kamelet.type": kameletType});
             integration.metadata.labels = meta;
+            if (selectedKamelet !== undefined && selectedKamelet !== '') {
+                const kamelet= KameletApi.getKamelets().filter(k => k.metadata.name === selectedKamelet).at(0);
+                if (kamelet) {
+                    (integration as any).spec = kamelet.spec;
+                    (integration as any).metadata.labels = kamelet.metadata.labels;
+                    (integration as any).metadata.annotations = kamelet.metadata.annotations;
+                    const i = CamelUtil.cloneIntegration(integration);
+                    return CamelDefinitionYaml.integrationToYaml(i);
+                }
+            }
             return CamelDefinitionYaml.integrationToYaml(integration);
         } else {
             return '';
@@ -112,51 +118,12 @@ export function CreateFileModal(props: Props) {
         : CamelUi.javaNameFromTitle(name);
     const fullFileName = filename + (isKamelet ? '-' + kameletType : '') + '.' + extension;
 
-    const selectOptions: React.JSX.Element[] = []
-    KameletApi.getKamelets()
+    const listOfValues: Value[] = KameletApi.getKamelets()
         .filter(k => k.metadata.labels["camel.apache.org/kamelet.type"] === kameletType)
-        .filter(k => k.spec.definition.title.toLowerCase().includes(inputValue?.toLowerCase() || ''))
-        .forEach((kamelet) => {
-        const s = <SelectOption key={kamelet.metadata.name}
-                                value={kamelet.metadata.name}
-                                description={kamelet.spec.definition.title}
-                                isFocused={kamelet.metadata.name === selectedKamelet[0]}
-                                onClick={event => {
-                                    setSelectedKamelet([kamelet.metadata.name, kamelet.spec.definition.title]);
-                                    setSelectIsOpen(false);
-                                    setInputValue(kamelet.spec.definition.title)
-                                }}
-        />;
-        selectOptions.push(s);
-    })
-
-
-    const toggle = (toggleRef: React.Ref<MenuToggleElement>) => (
-        <MenuToggle variant={"typeahead"}
-            isFullWidth
-            ref={toggleRef}
-            onClick={() => setSelectIsOpen(!selectIsOpen)}
-            isExpanded={selectIsOpen}
-            isDisabled={false}>
-            <TextInputGroup isPlain>
-                <TextInputGroupMain
-                    value={inputValue}
-                    onClick={event => {}}
-                    onChange={(event, value) => {
-                        setInputValue(value);
-                        setSelectIsOpen(true)
-                    }}
-                    id="typeahead-select-input"
-                    autoComplete="off"
-                    // innerRef={textInputRef}
-                    placeholder="Select Kamelet"
-                    role="combobox"
-                    isExpanded={selectIsOpen}
-                    aria-controls="select-typeahead-listbox"
-                />
-            </TextInputGroup>
-        </MenuToggle>
-    );
+        .map(k => {
+            const v: Value = {value: k.metadata.name, children: k.spec.definition.title}
+            return v;
+        })
 
     return (
         <Modal
@@ -191,7 +158,7 @@ export function CreateFileModal(props: Props) {
                                                     isSelected={kameletType === type}
                                                     onChange={(_, selected) => {
                                                         setKameletType(type as KameletTypes);
-                                                        setSelectedKamelet(['', ''])
+                                                        setSelectedKamelet(undefined)
                                                     }}/>
                         })}
                     </ToggleGroup>
@@ -205,18 +172,9 @@ export function CreateFileModal(props: Props) {
                     </FormHelperText>
                 </FormGroup>
                 {isKamelet && <FormGroup label="Copy from" fieldId="kamelet">
-                    <Select
-                        aria-label={"Kamelet"}
-                        onOpenChange={isOpen => setSelectIsOpen(false)}
-                        isOpen={selectIsOpen}
-                        aria-labelledby={"Kamelets"}
-                        toggle={toggle}
-                        shouldFocusToggleOnSelect
-                    >
-                        <SelectList>
-                            {selectOptions}
-                        </SelectList>
-                    </Select>
+                    <TypeaheadSelect listOfValues={listOfValues} onSelect={value => {
+                        setSelectedKamelet(value)
+                    }}/>
                 </FormGroup>}
             </Form>
         </Modal>