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/12/19 01:35:12 UTC

(camel-karavan) branch main updated (78b7fe4b -> 7abba71f)

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

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


    from 78b7fe4b Fix #955
     new 56748121 Fix #960
     new 7abba71f Fix #960

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../src/designer/property/DslProperties.tsx        | 100 ++++++++++++++++-----
 .../src/designer/property/usePropertiesHook.tsx    |  77 ++++++++++++----
 .../src/designer/route/useRouteDesignerHook.tsx    |   2 +-
 .../src/designer/property/DslProperties.tsx        | 100 ++++++++++++++++-----
 .../src/designer/property/usePropertiesHook.tsx    |  77 ++++++++++++----
 .../src/designer/route/useRouteDesignerHook.tsx    |   2 +-
 .../webui/src/designer/property/DslProperties.tsx  | 100 ++++++++++++++++-----
 .../src/designer/property/usePropertiesHook.tsx    |  77 ++++++++++++----
 .../src/designer/route/useRouteDesignerHook.tsx    |   2 +-
 9 files changed, 417 insertions(+), 120 deletions(-)


(camel-karavan) 01/02: Fix #960

Posted by ma...@apache.org.
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

commit 56748121fad06f00e642a400ad1c8e31092d2376
Author: Marat Gubaidullin <ma...@talismancloud.io>
AuthorDate: Mon Dec 18 20:34:54 2023 -0500

    Fix #960
---
 .../src/designer/property/DslProperties.tsx        | 100 ++++++++++++++++-----
 .../src/designer/property/usePropertiesHook.tsx    |  77 ++++++++++++----
 .../src/designer/route/useRouteDesignerHook.tsx    |   2 +-
 3 files changed, 139 insertions(+), 40 deletions(-)

diff --git a/karavan-designer/src/designer/property/DslProperties.tsx b/karavan-designer/src/designer/property/DslProperties.tsx
index 212a5f68..a3212312 100644
--- a/karavan-designer/src/designer/property/DslProperties.tsx
+++ b/karavan-designer/src/designer/property/DslProperties.tsx
@@ -14,12 +14,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React, {useState} from 'react';
+import React, {useEffect, useState} from 'react';
 import {
     Form,
     Text,
     Title,
-    TextVariants, ExpandableSection, Button, Tooltip,
+    TextVariants,
+    ExpandableSection,
+    Button,
+    Tooltip,
+    Dropdown,
+    MenuToggleElement,
+    MenuToggle,
+    DropdownList,
+    DropdownItem,
 } from '@patternfly/react-core';
 import '../karavan.css';
 import './DslProperties.css';
@@ -31,11 +39,11 @@ import {CamelUi} from "../utils/CamelUi";
 import {CamelMetadataApi, DataFormats, PropertyMeta} from "karavan-core/lib/model/CamelMetadata";
 import {IntegrationHeader} from "../utils/IntegrationHeader";
 import CloneIcon from "@patternfly/react-icons/dist/esm/icons/clone-icon";
-import ConvertIcon from "@patternfly/react-icons/dist/esm/icons/optimize-icon";
 import {useDesignerStore, useIntegrationStore} from "../DesignerStore";
 import {shallow} from "zustand/shallow";
 import {usePropertiesHook} from "./usePropertiesHook";
 import {CamelDisplayUtil} from "karavan-core/lib/api/CamelDisplayUtil";
+import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon';
 
 interface Props {
     designerType: 'routes' | 'rest' | 'beans'
@@ -45,7 +53,7 @@ export function DslProperties(props: Props) {
 
     const [integration] = useIntegrationStore((s) => [s.integration], shallow)
 
-    const {convertStep, cloneElement, onDataFormatChange, onPropertyChange, onParametersChange, onExpressionChange} =
+    const {saveAsRoute, convertStep, cloneElement, onDataFormatChange, onPropertyChange, onParametersChange, onExpressionChange} =
         usePropertiesHook(props.designerType);
 
     const [selectedStep, dark]
@@ -53,31 +61,83 @@ export function DslProperties(props: Props) {
 
     const [showAdvanced, setShowAdvanced] = useState<boolean>(false);
     const [isDescriptionExpanded, setIsDescriptionExpanded] = useState<boolean>(false);
+    const [isMenuOpen, setMenuOpen] = useState<boolean>(false);
+
+    useEffect(()=> {
+        setMenuOpen(false)
+    }, [selectedStep])
+
+    function getHeaderMenu(): JSX.Element {
+        const hasSteps = selectedStep?.hasSteps();
+        const targetDsl = CamelUi.getConvertTargetDsl(selectedStep?.dslName);
+        const targetDslTitle = targetDsl?.replace("Definition", "");
+        const showMenu = hasSteps || targetDsl !== undefined;
+        return showMenu ?
+            <Dropdown
+                style={{inset: "0px auto auto -70px important!"}}
+                className={"xxx"}
+                isOpen={isMenuOpen}
+                onSelect={() => {}}
+                onOpenChange={(isOpen: boolean) => setMenuOpen(isOpen)}
+                toggle={(toggleRef: React.Ref<MenuToggleElement>) => (
+                    <MenuToggle
+                        style={{width: "240px", display: "flex", flexDirection: "row", justifyContent: "end"}}
+                        className={"zzzz"}
+                        ref={toggleRef}
+                        aria-label="kebab dropdown toggle"
+                        variant="plain"
+                        onClick={() => setMenuOpen(!isMenuOpen)}
+                        isExpanded={isMenuOpen}
+                    >
+                        <EllipsisVIcon />
+                    </MenuToggle>
+                )}
+            >
+                <DropdownList >
+                    {hasSteps &&
+                        <DropdownItem key="saveRoute" onClick={(ev) => {
+                            ev.preventDefault()
+                            if (selectedStep) {
+                                saveAsRoute(selectedStep, true);
+                                setMenuOpen(false);
+                            }
+                        }}>
+                        Save Steps to Route
+                    </DropdownItem>}
+                    {hasSteps &&
+                        <DropdownItem key="saveRoute" onClick={(ev) => {
+                            ev.preventDefault()
+                            if (selectedStep) {
+                                saveAsRoute(selectedStep, false);
+                                setMenuOpen(false);
+                            }
+                        }}>
+                        Save Element to Route
+                        </DropdownItem>}
+                    {targetDsl &&
+                        <DropdownItem key="convert"
+                                   onClick={(ev) => {
+                                       ev.preventDefault()
+                                       if (selectedStep) {
+                                           convertStep(selectedStep, targetDsl);
+                                           setMenuOpen(false);
+                                       }
+                                   }}>
+                        Convert to {targetDslTitle}
+                    </DropdownItem>}
+                </DropdownList>
+            </Dropdown> : <></>;
+    }
 
     function getRouteHeader(): JSX.Element {
         const title = selectedStep && CamelDisplayUtil.getTitle(selectedStep)
         const description = selectedStep && CamelUi.getDescription(selectedStep);
         const descriptionLines: string [] = description ? description?.split("\n") : [""];
-        const targetDsl = CamelUi.getConvertTargetDsl(selectedStep?.dslName);
-        const targetDslTitle = targetDsl?.replace("Definition", "");
         return (
             <div className="headers">
                 <div className="top">
                     <Title headingLevel="h1" size="md">{title}</Title>
-                    {targetDsl &&
-                        <Button
-                            variant={"link"}
-                            icon={<ConvertIcon/>}
-                            iconPosition={"right"}
-                            onClick={event => {
-                                if (selectedStep) {
-                                    convertStep(selectedStep, targetDsl);
-                                }
-                            }}
-                        >
-                            Convert to {targetDslTitle}
-                        </Button>
-                    }
+                    {getHeaderMenu()}
                 </div>
                 <Text component={TextVariants.p}>{descriptionLines.at(0)}</Text>
                 {descriptionLines.length > 1 &&
diff --git a/karavan-designer/src/designer/property/usePropertiesHook.tsx b/karavan-designer/src/designer/property/usePropertiesHook.tsx
index f8b94498..a0b6d445 100644
--- a/karavan-designer/src/designer/property/usePropertiesHook.tsx
+++ b/karavan-designer/src/designer/property/usePropertiesHook.tsx
@@ -17,7 +17,7 @@
 import '../karavan.css';
 import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
 import {
-    DataFormatDefinition, ExpressionDefinition, ToDefinition,
+    DataFormatDefinition, ExpressionDefinition, FromDefinition, ToDefinition,
 } from "karavan-core/lib/model/CamelDefinition";
 import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
 import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
@@ -28,13 +28,13 @@ import {shallow} from "zustand/shallow";
 import {CamelMetadataApi} from "karavan-core/lib/model/CamelMetadata";
 import {EventBus} from "../utils/EventBus";
 
-export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = 'routes') {
+export function usePropertiesHook(designerType: 'routes' | 'rest' | 'beans' = 'routes') {
 
     const [integration, setIntegration] = useIntegrationStore((state) => [state.integration, state.setIntegration], shallow)
-    const [ selectedStep, setSelectedStep, setSelectedUuids] = useDesignerStore((s) =>
+    const [selectedStep, setSelectedStep, setSelectedUuids] = useDesignerStore((s) =>
         [s.selectedStep, s.setSelectedStep, s.setSelectedUuids], shallow)
 
-    function onPropertyUpdate (element: CamelElement, newRoute?: RouteToCreate) {
+    function onPropertyUpdate(element: CamelElement, newRoute?: RouteToCreate) {
         if (designerType === 'routes') {
             onRoutePropertyUpdate(element, newRoute);
         } else if (designerType === 'rest') {
@@ -44,10 +44,13 @@ export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = '
         }
     }
 
-    function onRoutePropertyUpdate (element: CamelElement, newRoute?: RouteToCreate) {
+    function onRoutePropertyUpdate(element: CamelElement, newRoute?: RouteToCreate) {
         if (newRoute) {
             let i = CamelDefinitionApiExt.updateIntegrationRouteElement(integration, element);
-            const f = CamelDefinitionApi.createFromDefinition({uri: newRoute.componentName, parameters: {name: newRoute.name}});
+            const f = CamelDefinitionApi.createFromDefinition({
+                uri: newRoute.componentName,
+                parameters: {name: newRoute.name}
+            });
             const r = CamelDefinitionApi.createRouteDefinition({from: f, id: newRoute.name})
             i = CamelDefinitionApiExt.addStepToIntegration(i, r, '');
             const clone = CamelUtil.cloneIntegration(i);
@@ -61,10 +64,13 @@ export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = '
         }
     }
 
-    function onRestPropertyUpdate (element: CamelElement, newRoute?: RouteToCreate) {
+    function onRestPropertyUpdate(element: CamelElement, newRoute?: RouteToCreate) {
         if (newRoute) {
             let i = CamelDefinitionApiExt.updateIntegrationRestElement(integration, element);
-            const f = CamelDefinitionApi.createFromDefinition({uri: newRoute.componentName, parameters: {name: newRoute.name}});
+            const f = CamelDefinitionApi.createFromDefinition({
+                uri: newRoute.componentName,
+                parameters: {name: newRoute.name}
+            });
             const r = CamelDefinitionApi.createRouteDefinition({from: f, id: newRoute.name})
             i = CamelDefinitionApiExt.addStepToIntegration(i, r, '');
             const clone = CamelUtil.cloneIntegration(i);
@@ -78,10 +84,13 @@ export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = '
         }
     }
 
-    function onBeanPropertyUpdate (element: CamelElement, newRoute?: RouteToCreate) {
+    function onBeanPropertyUpdate(element: CamelElement, newRoute?: RouteToCreate) {
         if (newRoute) {
             let i = CamelDefinitionApiExt.updateIntegrationBeanElement(integration, element);
-            const f = CamelDefinitionApi.createFromDefinition({uri: newRoute.componentName, parameters: {name: newRoute.name}});
+            const f = CamelDefinitionApi.createFromDefinition({
+                uri: newRoute.componentName,
+                parameters: {name: newRoute.name}
+            });
             const r = CamelDefinitionApi.createRouteDefinition({from: f, id: newRoute.name})
             i = CamelDefinitionApiExt.addStepToIntegration(i, r, '');
             const clone = CamelUtil.cloneIntegration(i);
@@ -95,7 +104,7 @@ export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = '
         }
     }
 
-    function onPropertyChange (fieldId: string, value: string | number | boolean | any, newRoute?: RouteToCreate){
+    function onPropertyChange(fieldId: string, value: string | number | boolean | any, newRoute?: RouteToCreate) {
         value = value === '' ? undefined : value;
         if (selectedStep) {
             const clone = CamelUtil.cloneStep(selectedStep);
@@ -105,13 +114,13 @@ export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = '
         }
     }
 
-    function onDataFormatChange (value: DataFormatDefinition)   {
+    function onDataFormatChange(value: DataFormatDefinition) {
         value.uuid = selectedStep?.uuid ? selectedStep?.uuid : value.uuid;
         setSelectedStep(value);
         onPropertyUpdate(value);
     }
 
-    function onExpressionChange (propertyName: string, exp: ExpressionDefinition)   {
+    function onExpressionChange(propertyName: string, exp: ExpressionDefinition) {
         if (selectedStep) {
             const clone = (CamelUtil.cloneStep(selectedStep));
             (clone as any)[propertyName] = exp;
@@ -120,7 +129,7 @@ export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = '
         }
     }
 
-    function onParametersChange (parameter: string, value: string | number | boolean | any, pathParameter?: boolean, newRoute?: RouteToCreate)   {
+    function onParametersChange(parameter: string, value: string | number | boolean | any, pathParameter?: boolean, newRoute?: RouteToCreate) {
         value = value === '' ? undefined : value;
         if (selectedStep) {
             const clone = (CamelUtil.cloneStep(selectedStep));
@@ -143,11 +152,28 @@ export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = '
         }
     }
 
-    function cloneElement ()   {
+    function cloneElement() {
         // TODO:
     }
 
-    const convertStep = (step: CamelElement, targetDslName: string ) => {
+    function saveAsRoute(step: CamelElement, stepsOnly: boolean) {
+        if (step && step.hasSteps()) {
+            const stepClone = CamelUtil.cloneStep(step, true);
+            const from = CamelDefinitionApi.createFromDefinition({uri: "direct", parameters: {name: (step as any).id}});
+            if (stepsOnly) {
+                from.steps = (stepClone as any).steps;
+            } else {
+                from.steps = [stepClone]
+            }
+            const route = CamelDefinitionApi.createRouteDefinition({from: from, nodePrefixId: (step as any).id});
+            const clone = CamelUtil.cloneIntegration(integration);
+            clone.spec.flows?.push(route)
+            setIntegration(clone, false);
+            // setSelectedStep(element);
+        }
+    }
+
+    const convertStep = (step: CamelElement, targetDslName: string) => {
         try {
             // setSelectedStep(undefined);
             if (targetDslName === 'ChoiceDefinition' && step.dslName === 'FilterDefinition') {
@@ -156,7 +182,11 @@ export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = '
                 delete (clone as any).stepName;
                 const when = CamelDefinitionApi.createWhenDefinition(clone);
                 const otherwise = CamelDefinitionApi.createOtherwiseDefinition(undefined);
-                const choice = CamelDefinitionApi.createChoiceDefinition({uuid: step.uuid, when: [when], otherwise: otherwise});
+                const choice = CamelDefinitionApi.createChoiceDefinition({
+                    uuid: step.uuid,
+                    when: [when],
+                    otherwise: otherwise
+                });
                 onPropertyUpdate(choice);
                 setSelectedStep(choice);
             } else {
@@ -171,7 +201,7 @@ export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = '
                 })
                 delete (clone as any).dslName;
                 delete (clone as any).stepName;
-                const converted= CamelDefinitionApi.createStep(targetDslName, clone, true);
+                const converted = CamelDefinitionApi.createStep(targetDslName, clone, true);
                 onPropertyUpdate(converted);
                 setSelectedStep(converted);
             }
@@ -180,5 +210,14 @@ export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = '
         }
     }
 
-    return {convertStep, cloneElement, onPropertyChange, onParametersChange, onDataFormatChange, onExpressionChange, getInternalComponentName}
+    return {
+        saveAsRoute,
+        convertStep,
+        cloneElement,
+        onPropertyChange,
+        onParametersChange,
+        onDataFormatChange,
+        onExpressionChange,
+        getInternalComponentName
+    }
 }
\ No newline at end of file
diff --git a/karavan-designer/src/designer/route/useRouteDesignerHook.tsx b/karavan-designer/src/designer/route/useRouteDesignerHook.tsx
index 192988ab..543f2a00 100644
--- a/karavan-designer/src/designer/route/useRouteDesignerHook.tsx
+++ b/karavan-designer/src/designer/route/useRouteDesignerHook.tsx
@@ -220,7 +220,7 @@ export function useRouteDesignerHook () {
         }
     }
 
-    const openSelector = (parentId: string | undefined, parentDsl: string | undefined, showSteps: boolean = true, position?: number | undefined, selectorTabIndex?: string | number) => {
+    const openSelector = (parentId: string | undefined, parentDsl: string | undefined, showSteps: boolean = true, position?: number | undefined) => {
         setShowSelector(true);
         setParentId(parentId || '');
         setParentDsl(parentDsl);


(camel-karavan) 02/02: Fix #960

Posted by ma...@apache.org.
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

commit 7abba71f74fb1c44c1738657082bff9f34ee170c
Author: Marat Gubaidullin <ma...@talismancloud.io>
AuthorDate: Mon Dec 18 20:35:04 2023 -0500

    Fix #960
---
 .../src/designer/property/DslProperties.tsx        | 100 ++++++++++++++++-----
 .../src/designer/property/usePropertiesHook.tsx    |  77 ++++++++++++----
 .../src/designer/route/useRouteDesignerHook.tsx    |   2 +-
 .../webui/src/designer/property/DslProperties.tsx  | 100 ++++++++++++++++-----
 .../src/designer/property/usePropertiesHook.tsx    |  77 ++++++++++++----
 .../src/designer/route/useRouteDesignerHook.tsx    |   2 +-
 6 files changed, 278 insertions(+), 80 deletions(-)

diff --git a/karavan-space/src/designer/property/DslProperties.tsx b/karavan-space/src/designer/property/DslProperties.tsx
index 212a5f68..a3212312 100644
--- a/karavan-space/src/designer/property/DslProperties.tsx
+++ b/karavan-space/src/designer/property/DslProperties.tsx
@@ -14,12 +14,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React, {useState} from 'react';
+import React, {useEffect, useState} from 'react';
 import {
     Form,
     Text,
     Title,
-    TextVariants, ExpandableSection, Button, Tooltip,
+    TextVariants,
+    ExpandableSection,
+    Button,
+    Tooltip,
+    Dropdown,
+    MenuToggleElement,
+    MenuToggle,
+    DropdownList,
+    DropdownItem,
 } from '@patternfly/react-core';
 import '../karavan.css';
 import './DslProperties.css';
@@ -31,11 +39,11 @@ import {CamelUi} from "../utils/CamelUi";
 import {CamelMetadataApi, DataFormats, PropertyMeta} from "karavan-core/lib/model/CamelMetadata";
 import {IntegrationHeader} from "../utils/IntegrationHeader";
 import CloneIcon from "@patternfly/react-icons/dist/esm/icons/clone-icon";
-import ConvertIcon from "@patternfly/react-icons/dist/esm/icons/optimize-icon";
 import {useDesignerStore, useIntegrationStore} from "../DesignerStore";
 import {shallow} from "zustand/shallow";
 import {usePropertiesHook} from "./usePropertiesHook";
 import {CamelDisplayUtil} from "karavan-core/lib/api/CamelDisplayUtil";
+import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon';
 
 interface Props {
     designerType: 'routes' | 'rest' | 'beans'
@@ -45,7 +53,7 @@ export function DslProperties(props: Props) {
 
     const [integration] = useIntegrationStore((s) => [s.integration], shallow)
 
-    const {convertStep, cloneElement, onDataFormatChange, onPropertyChange, onParametersChange, onExpressionChange} =
+    const {saveAsRoute, convertStep, cloneElement, onDataFormatChange, onPropertyChange, onParametersChange, onExpressionChange} =
         usePropertiesHook(props.designerType);
 
     const [selectedStep, dark]
@@ -53,31 +61,83 @@ export function DslProperties(props: Props) {
 
     const [showAdvanced, setShowAdvanced] = useState<boolean>(false);
     const [isDescriptionExpanded, setIsDescriptionExpanded] = useState<boolean>(false);
+    const [isMenuOpen, setMenuOpen] = useState<boolean>(false);
+
+    useEffect(()=> {
+        setMenuOpen(false)
+    }, [selectedStep])
+
+    function getHeaderMenu(): JSX.Element {
+        const hasSteps = selectedStep?.hasSteps();
+        const targetDsl = CamelUi.getConvertTargetDsl(selectedStep?.dslName);
+        const targetDslTitle = targetDsl?.replace("Definition", "");
+        const showMenu = hasSteps || targetDsl !== undefined;
+        return showMenu ?
+            <Dropdown
+                style={{inset: "0px auto auto -70px important!"}}
+                className={"xxx"}
+                isOpen={isMenuOpen}
+                onSelect={() => {}}
+                onOpenChange={(isOpen: boolean) => setMenuOpen(isOpen)}
+                toggle={(toggleRef: React.Ref<MenuToggleElement>) => (
+                    <MenuToggle
+                        style={{width: "240px", display: "flex", flexDirection: "row", justifyContent: "end"}}
+                        className={"zzzz"}
+                        ref={toggleRef}
+                        aria-label="kebab dropdown toggle"
+                        variant="plain"
+                        onClick={() => setMenuOpen(!isMenuOpen)}
+                        isExpanded={isMenuOpen}
+                    >
+                        <EllipsisVIcon />
+                    </MenuToggle>
+                )}
+            >
+                <DropdownList >
+                    {hasSteps &&
+                        <DropdownItem key="saveRoute" onClick={(ev) => {
+                            ev.preventDefault()
+                            if (selectedStep) {
+                                saveAsRoute(selectedStep, true);
+                                setMenuOpen(false);
+                            }
+                        }}>
+                        Save Steps to Route
+                    </DropdownItem>}
+                    {hasSteps &&
+                        <DropdownItem key="saveRoute" onClick={(ev) => {
+                            ev.preventDefault()
+                            if (selectedStep) {
+                                saveAsRoute(selectedStep, false);
+                                setMenuOpen(false);
+                            }
+                        }}>
+                        Save Element to Route
+                        </DropdownItem>}
+                    {targetDsl &&
+                        <DropdownItem key="convert"
+                                   onClick={(ev) => {
+                                       ev.preventDefault()
+                                       if (selectedStep) {
+                                           convertStep(selectedStep, targetDsl);
+                                           setMenuOpen(false);
+                                       }
+                                   }}>
+                        Convert to {targetDslTitle}
+                    </DropdownItem>}
+                </DropdownList>
+            </Dropdown> : <></>;
+    }
 
     function getRouteHeader(): JSX.Element {
         const title = selectedStep && CamelDisplayUtil.getTitle(selectedStep)
         const description = selectedStep && CamelUi.getDescription(selectedStep);
         const descriptionLines: string [] = description ? description?.split("\n") : [""];
-        const targetDsl = CamelUi.getConvertTargetDsl(selectedStep?.dslName);
-        const targetDslTitle = targetDsl?.replace("Definition", "");
         return (
             <div className="headers">
                 <div className="top">
                     <Title headingLevel="h1" size="md">{title}</Title>
-                    {targetDsl &&
-                        <Button
-                            variant={"link"}
-                            icon={<ConvertIcon/>}
-                            iconPosition={"right"}
-                            onClick={event => {
-                                if (selectedStep) {
-                                    convertStep(selectedStep, targetDsl);
-                                }
-                            }}
-                        >
-                            Convert to {targetDslTitle}
-                        </Button>
-                    }
+                    {getHeaderMenu()}
                 </div>
                 <Text component={TextVariants.p}>{descriptionLines.at(0)}</Text>
                 {descriptionLines.length > 1 &&
diff --git a/karavan-space/src/designer/property/usePropertiesHook.tsx b/karavan-space/src/designer/property/usePropertiesHook.tsx
index f8b94498..a0b6d445 100644
--- a/karavan-space/src/designer/property/usePropertiesHook.tsx
+++ b/karavan-space/src/designer/property/usePropertiesHook.tsx
@@ -17,7 +17,7 @@
 import '../karavan.css';
 import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
 import {
-    DataFormatDefinition, ExpressionDefinition, ToDefinition,
+    DataFormatDefinition, ExpressionDefinition, FromDefinition, ToDefinition,
 } from "karavan-core/lib/model/CamelDefinition";
 import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
 import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
@@ -28,13 +28,13 @@ import {shallow} from "zustand/shallow";
 import {CamelMetadataApi} from "karavan-core/lib/model/CamelMetadata";
 import {EventBus} from "../utils/EventBus";
 
-export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = 'routes') {
+export function usePropertiesHook(designerType: 'routes' | 'rest' | 'beans' = 'routes') {
 
     const [integration, setIntegration] = useIntegrationStore((state) => [state.integration, state.setIntegration], shallow)
-    const [ selectedStep, setSelectedStep, setSelectedUuids] = useDesignerStore((s) =>
+    const [selectedStep, setSelectedStep, setSelectedUuids] = useDesignerStore((s) =>
         [s.selectedStep, s.setSelectedStep, s.setSelectedUuids], shallow)
 
-    function onPropertyUpdate (element: CamelElement, newRoute?: RouteToCreate) {
+    function onPropertyUpdate(element: CamelElement, newRoute?: RouteToCreate) {
         if (designerType === 'routes') {
             onRoutePropertyUpdate(element, newRoute);
         } else if (designerType === 'rest') {
@@ -44,10 +44,13 @@ export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = '
         }
     }
 
-    function onRoutePropertyUpdate (element: CamelElement, newRoute?: RouteToCreate) {
+    function onRoutePropertyUpdate(element: CamelElement, newRoute?: RouteToCreate) {
         if (newRoute) {
             let i = CamelDefinitionApiExt.updateIntegrationRouteElement(integration, element);
-            const f = CamelDefinitionApi.createFromDefinition({uri: newRoute.componentName, parameters: {name: newRoute.name}});
+            const f = CamelDefinitionApi.createFromDefinition({
+                uri: newRoute.componentName,
+                parameters: {name: newRoute.name}
+            });
             const r = CamelDefinitionApi.createRouteDefinition({from: f, id: newRoute.name})
             i = CamelDefinitionApiExt.addStepToIntegration(i, r, '');
             const clone = CamelUtil.cloneIntegration(i);
@@ -61,10 +64,13 @@ export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = '
         }
     }
 
-    function onRestPropertyUpdate (element: CamelElement, newRoute?: RouteToCreate) {
+    function onRestPropertyUpdate(element: CamelElement, newRoute?: RouteToCreate) {
         if (newRoute) {
             let i = CamelDefinitionApiExt.updateIntegrationRestElement(integration, element);
-            const f = CamelDefinitionApi.createFromDefinition({uri: newRoute.componentName, parameters: {name: newRoute.name}});
+            const f = CamelDefinitionApi.createFromDefinition({
+                uri: newRoute.componentName,
+                parameters: {name: newRoute.name}
+            });
             const r = CamelDefinitionApi.createRouteDefinition({from: f, id: newRoute.name})
             i = CamelDefinitionApiExt.addStepToIntegration(i, r, '');
             const clone = CamelUtil.cloneIntegration(i);
@@ -78,10 +84,13 @@ export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = '
         }
     }
 
-    function onBeanPropertyUpdate (element: CamelElement, newRoute?: RouteToCreate) {
+    function onBeanPropertyUpdate(element: CamelElement, newRoute?: RouteToCreate) {
         if (newRoute) {
             let i = CamelDefinitionApiExt.updateIntegrationBeanElement(integration, element);
-            const f = CamelDefinitionApi.createFromDefinition({uri: newRoute.componentName, parameters: {name: newRoute.name}});
+            const f = CamelDefinitionApi.createFromDefinition({
+                uri: newRoute.componentName,
+                parameters: {name: newRoute.name}
+            });
             const r = CamelDefinitionApi.createRouteDefinition({from: f, id: newRoute.name})
             i = CamelDefinitionApiExt.addStepToIntegration(i, r, '');
             const clone = CamelUtil.cloneIntegration(i);
@@ -95,7 +104,7 @@ export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = '
         }
     }
 
-    function onPropertyChange (fieldId: string, value: string | number | boolean | any, newRoute?: RouteToCreate){
+    function onPropertyChange(fieldId: string, value: string | number | boolean | any, newRoute?: RouteToCreate) {
         value = value === '' ? undefined : value;
         if (selectedStep) {
             const clone = CamelUtil.cloneStep(selectedStep);
@@ -105,13 +114,13 @@ export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = '
         }
     }
 
-    function onDataFormatChange (value: DataFormatDefinition)   {
+    function onDataFormatChange(value: DataFormatDefinition) {
         value.uuid = selectedStep?.uuid ? selectedStep?.uuid : value.uuid;
         setSelectedStep(value);
         onPropertyUpdate(value);
     }
 
-    function onExpressionChange (propertyName: string, exp: ExpressionDefinition)   {
+    function onExpressionChange(propertyName: string, exp: ExpressionDefinition) {
         if (selectedStep) {
             const clone = (CamelUtil.cloneStep(selectedStep));
             (clone as any)[propertyName] = exp;
@@ -120,7 +129,7 @@ export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = '
         }
     }
 
-    function onParametersChange (parameter: string, value: string | number | boolean | any, pathParameter?: boolean, newRoute?: RouteToCreate)   {
+    function onParametersChange(parameter: string, value: string | number | boolean | any, pathParameter?: boolean, newRoute?: RouteToCreate) {
         value = value === '' ? undefined : value;
         if (selectedStep) {
             const clone = (CamelUtil.cloneStep(selectedStep));
@@ -143,11 +152,28 @@ export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = '
         }
     }
 
-    function cloneElement ()   {
+    function cloneElement() {
         // TODO:
     }
 
-    const convertStep = (step: CamelElement, targetDslName: string ) => {
+    function saveAsRoute(step: CamelElement, stepsOnly: boolean) {
+        if (step && step.hasSteps()) {
+            const stepClone = CamelUtil.cloneStep(step, true);
+            const from = CamelDefinitionApi.createFromDefinition({uri: "direct", parameters: {name: (step as any).id}});
+            if (stepsOnly) {
+                from.steps = (stepClone as any).steps;
+            } else {
+                from.steps = [stepClone]
+            }
+            const route = CamelDefinitionApi.createRouteDefinition({from: from, nodePrefixId: (step as any).id});
+            const clone = CamelUtil.cloneIntegration(integration);
+            clone.spec.flows?.push(route)
+            setIntegration(clone, false);
+            // setSelectedStep(element);
+        }
+    }
+
+    const convertStep = (step: CamelElement, targetDslName: string) => {
         try {
             // setSelectedStep(undefined);
             if (targetDslName === 'ChoiceDefinition' && step.dslName === 'FilterDefinition') {
@@ -156,7 +182,11 @@ export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = '
                 delete (clone as any).stepName;
                 const when = CamelDefinitionApi.createWhenDefinition(clone);
                 const otherwise = CamelDefinitionApi.createOtherwiseDefinition(undefined);
-                const choice = CamelDefinitionApi.createChoiceDefinition({uuid: step.uuid, when: [when], otherwise: otherwise});
+                const choice = CamelDefinitionApi.createChoiceDefinition({
+                    uuid: step.uuid,
+                    when: [when],
+                    otherwise: otherwise
+                });
                 onPropertyUpdate(choice);
                 setSelectedStep(choice);
             } else {
@@ -171,7 +201,7 @@ export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = '
                 })
                 delete (clone as any).dslName;
                 delete (clone as any).stepName;
-                const converted= CamelDefinitionApi.createStep(targetDslName, clone, true);
+                const converted = CamelDefinitionApi.createStep(targetDslName, clone, true);
                 onPropertyUpdate(converted);
                 setSelectedStep(converted);
             }
@@ -180,5 +210,14 @@ export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = '
         }
     }
 
-    return {convertStep, cloneElement, onPropertyChange, onParametersChange, onDataFormatChange, onExpressionChange, getInternalComponentName}
+    return {
+        saveAsRoute,
+        convertStep,
+        cloneElement,
+        onPropertyChange,
+        onParametersChange,
+        onDataFormatChange,
+        onExpressionChange,
+        getInternalComponentName
+    }
 }
\ No newline at end of file
diff --git a/karavan-space/src/designer/route/useRouteDesignerHook.tsx b/karavan-space/src/designer/route/useRouteDesignerHook.tsx
index 192988ab..543f2a00 100644
--- a/karavan-space/src/designer/route/useRouteDesignerHook.tsx
+++ b/karavan-space/src/designer/route/useRouteDesignerHook.tsx
@@ -220,7 +220,7 @@ export function useRouteDesignerHook () {
         }
     }
 
-    const openSelector = (parentId: string | undefined, parentDsl: string | undefined, showSteps: boolean = true, position?: number | undefined, selectorTabIndex?: string | number) => {
+    const openSelector = (parentId: string | undefined, parentDsl: string | undefined, showSteps: boolean = true, position?: number | undefined) => {
         setShowSelector(true);
         setParentId(parentId || '');
         setParentDsl(parentDsl);
diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/property/DslProperties.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/property/DslProperties.tsx
index 212a5f68..a3212312 100644
--- a/karavan-web/karavan-app/src/main/webui/src/designer/property/DslProperties.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/property/DslProperties.tsx
@@ -14,12 +14,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React, {useState} from 'react';
+import React, {useEffect, useState} from 'react';
 import {
     Form,
     Text,
     Title,
-    TextVariants, ExpandableSection, Button, Tooltip,
+    TextVariants,
+    ExpandableSection,
+    Button,
+    Tooltip,
+    Dropdown,
+    MenuToggleElement,
+    MenuToggle,
+    DropdownList,
+    DropdownItem,
 } from '@patternfly/react-core';
 import '../karavan.css';
 import './DslProperties.css';
@@ -31,11 +39,11 @@ import {CamelUi} from "../utils/CamelUi";
 import {CamelMetadataApi, DataFormats, PropertyMeta} from "karavan-core/lib/model/CamelMetadata";
 import {IntegrationHeader} from "../utils/IntegrationHeader";
 import CloneIcon from "@patternfly/react-icons/dist/esm/icons/clone-icon";
-import ConvertIcon from "@patternfly/react-icons/dist/esm/icons/optimize-icon";
 import {useDesignerStore, useIntegrationStore} from "../DesignerStore";
 import {shallow} from "zustand/shallow";
 import {usePropertiesHook} from "./usePropertiesHook";
 import {CamelDisplayUtil} from "karavan-core/lib/api/CamelDisplayUtil";
+import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon';
 
 interface Props {
     designerType: 'routes' | 'rest' | 'beans'
@@ -45,7 +53,7 @@ export function DslProperties(props: Props) {
 
     const [integration] = useIntegrationStore((s) => [s.integration], shallow)
 
-    const {convertStep, cloneElement, onDataFormatChange, onPropertyChange, onParametersChange, onExpressionChange} =
+    const {saveAsRoute, convertStep, cloneElement, onDataFormatChange, onPropertyChange, onParametersChange, onExpressionChange} =
         usePropertiesHook(props.designerType);
 
     const [selectedStep, dark]
@@ -53,31 +61,83 @@ export function DslProperties(props: Props) {
 
     const [showAdvanced, setShowAdvanced] = useState<boolean>(false);
     const [isDescriptionExpanded, setIsDescriptionExpanded] = useState<boolean>(false);
+    const [isMenuOpen, setMenuOpen] = useState<boolean>(false);
+
+    useEffect(()=> {
+        setMenuOpen(false)
+    }, [selectedStep])
+
+    function getHeaderMenu(): JSX.Element {
+        const hasSteps = selectedStep?.hasSteps();
+        const targetDsl = CamelUi.getConvertTargetDsl(selectedStep?.dslName);
+        const targetDslTitle = targetDsl?.replace("Definition", "");
+        const showMenu = hasSteps || targetDsl !== undefined;
+        return showMenu ?
+            <Dropdown
+                style={{inset: "0px auto auto -70px important!"}}
+                className={"xxx"}
+                isOpen={isMenuOpen}
+                onSelect={() => {}}
+                onOpenChange={(isOpen: boolean) => setMenuOpen(isOpen)}
+                toggle={(toggleRef: React.Ref<MenuToggleElement>) => (
+                    <MenuToggle
+                        style={{width: "240px", display: "flex", flexDirection: "row", justifyContent: "end"}}
+                        className={"zzzz"}
+                        ref={toggleRef}
+                        aria-label="kebab dropdown toggle"
+                        variant="plain"
+                        onClick={() => setMenuOpen(!isMenuOpen)}
+                        isExpanded={isMenuOpen}
+                    >
+                        <EllipsisVIcon />
+                    </MenuToggle>
+                )}
+            >
+                <DropdownList >
+                    {hasSteps &&
+                        <DropdownItem key="saveRoute" onClick={(ev) => {
+                            ev.preventDefault()
+                            if (selectedStep) {
+                                saveAsRoute(selectedStep, true);
+                                setMenuOpen(false);
+                            }
+                        }}>
+                        Save Steps to Route
+                    </DropdownItem>}
+                    {hasSteps &&
+                        <DropdownItem key="saveRoute" onClick={(ev) => {
+                            ev.preventDefault()
+                            if (selectedStep) {
+                                saveAsRoute(selectedStep, false);
+                                setMenuOpen(false);
+                            }
+                        }}>
+                        Save Element to Route
+                        </DropdownItem>}
+                    {targetDsl &&
+                        <DropdownItem key="convert"
+                                   onClick={(ev) => {
+                                       ev.preventDefault()
+                                       if (selectedStep) {
+                                           convertStep(selectedStep, targetDsl);
+                                           setMenuOpen(false);
+                                       }
+                                   }}>
+                        Convert to {targetDslTitle}
+                    </DropdownItem>}
+                </DropdownList>
+            </Dropdown> : <></>;
+    }
 
     function getRouteHeader(): JSX.Element {
         const title = selectedStep && CamelDisplayUtil.getTitle(selectedStep)
         const description = selectedStep && CamelUi.getDescription(selectedStep);
         const descriptionLines: string [] = description ? description?.split("\n") : [""];
-        const targetDsl = CamelUi.getConvertTargetDsl(selectedStep?.dslName);
-        const targetDslTitle = targetDsl?.replace("Definition", "");
         return (
             <div className="headers">
                 <div className="top">
                     <Title headingLevel="h1" size="md">{title}</Title>
-                    {targetDsl &&
-                        <Button
-                            variant={"link"}
-                            icon={<ConvertIcon/>}
-                            iconPosition={"right"}
-                            onClick={event => {
-                                if (selectedStep) {
-                                    convertStep(selectedStep, targetDsl);
-                                }
-                            }}
-                        >
-                            Convert to {targetDslTitle}
-                        </Button>
-                    }
+                    {getHeaderMenu()}
                 </div>
                 <Text component={TextVariants.p}>{descriptionLines.at(0)}</Text>
                 {descriptionLines.length > 1 &&
diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/property/usePropertiesHook.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/property/usePropertiesHook.tsx
index f8b94498..a0b6d445 100644
--- a/karavan-web/karavan-app/src/main/webui/src/designer/property/usePropertiesHook.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/property/usePropertiesHook.tsx
@@ -17,7 +17,7 @@
 import '../karavan.css';
 import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
 import {
-    DataFormatDefinition, ExpressionDefinition, ToDefinition,
+    DataFormatDefinition, ExpressionDefinition, FromDefinition, ToDefinition,
 } from "karavan-core/lib/model/CamelDefinition";
 import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
 import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
@@ -28,13 +28,13 @@ import {shallow} from "zustand/shallow";
 import {CamelMetadataApi} from "karavan-core/lib/model/CamelMetadata";
 import {EventBus} from "../utils/EventBus";
 
-export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = 'routes') {
+export function usePropertiesHook(designerType: 'routes' | 'rest' | 'beans' = 'routes') {
 
     const [integration, setIntegration] = useIntegrationStore((state) => [state.integration, state.setIntegration], shallow)
-    const [ selectedStep, setSelectedStep, setSelectedUuids] = useDesignerStore((s) =>
+    const [selectedStep, setSelectedStep, setSelectedUuids] = useDesignerStore((s) =>
         [s.selectedStep, s.setSelectedStep, s.setSelectedUuids], shallow)
 
-    function onPropertyUpdate (element: CamelElement, newRoute?: RouteToCreate) {
+    function onPropertyUpdate(element: CamelElement, newRoute?: RouteToCreate) {
         if (designerType === 'routes') {
             onRoutePropertyUpdate(element, newRoute);
         } else if (designerType === 'rest') {
@@ -44,10 +44,13 @@ export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = '
         }
     }
 
-    function onRoutePropertyUpdate (element: CamelElement, newRoute?: RouteToCreate) {
+    function onRoutePropertyUpdate(element: CamelElement, newRoute?: RouteToCreate) {
         if (newRoute) {
             let i = CamelDefinitionApiExt.updateIntegrationRouteElement(integration, element);
-            const f = CamelDefinitionApi.createFromDefinition({uri: newRoute.componentName, parameters: {name: newRoute.name}});
+            const f = CamelDefinitionApi.createFromDefinition({
+                uri: newRoute.componentName,
+                parameters: {name: newRoute.name}
+            });
             const r = CamelDefinitionApi.createRouteDefinition({from: f, id: newRoute.name})
             i = CamelDefinitionApiExt.addStepToIntegration(i, r, '');
             const clone = CamelUtil.cloneIntegration(i);
@@ -61,10 +64,13 @@ export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = '
         }
     }
 
-    function onRestPropertyUpdate (element: CamelElement, newRoute?: RouteToCreate) {
+    function onRestPropertyUpdate(element: CamelElement, newRoute?: RouteToCreate) {
         if (newRoute) {
             let i = CamelDefinitionApiExt.updateIntegrationRestElement(integration, element);
-            const f = CamelDefinitionApi.createFromDefinition({uri: newRoute.componentName, parameters: {name: newRoute.name}});
+            const f = CamelDefinitionApi.createFromDefinition({
+                uri: newRoute.componentName,
+                parameters: {name: newRoute.name}
+            });
             const r = CamelDefinitionApi.createRouteDefinition({from: f, id: newRoute.name})
             i = CamelDefinitionApiExt.addStepToIntegration(i, r, '');
             const clone = CamelUtil.cloneIntegration(i);
@@ -78,10 +84,13 @@ export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = '
         }
     }
 
-    function onBeanPropertyUpdate (element: CamelElement, newRoute?: RouteToCreate) {
+    function onBeanPropertyUpdate(element: CamelElement, newRoute?: RouteToCreate) {
         if (newRoute) {
             let i = CamelDefinitionApiExt.updateIntegrationBeanElement(integration, element);
-            const f = CamelDefinitionApi.createFromDefinition({uri: newRoute.componentName, parameters: {name: newRoute.name}});
+            const f = CamelDefinitionApi.createFromDefinition({
+                uri: newRoute.componentName,
+                parameters: {name: newRoute.name}
+            });
             const r = CamelDefinitionApi.createRouteDefinition({from: f, id: newRoute.name})
             i = CamelDefinitionApiExt.addStepToIntegration(i, r, '');
             const clone = CamelUtil.cloneIntegration(i);
@@ -95,7 +104,7 @@ export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = '
         }
     }
 
-    function onPropertyChange (fieldId: string, value: string | number | boolean | any, newRoute?: RouteToCreate){
+    function onPropertyChange(fieldId: string, value: string | number | boolean | any, newRoute?: RouteToCreate) {
         value = value === '' ? undefined : value;
         if (selectedStep) {
             const clone = CamelUtil.cloneStep(selectedStep);
@@ -105,13 +114,13 @@ export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = '
         }
     }
 
-    function onDataFormatChange (value: DataFormatDefinition)   {
+    function onDataFormatChange(value: DataFormatDefinition) {
         value.uuid = selectedStep?.uuid ? selectedStep?.uuid : value.uuid;
         setSelectedStep(value);
         onPropertyUpdate(value);
     }
 
-    function onExpressionChange (propertyName: string, exp: ExpressionDefinition)   {
+    function onExpressionChange(propertyName: string, exp: ExpressionDefinition) {
         if (selectedStep) {
             const clone = (CamelUtil.cloneStep(selectedStep));
             (clone as any)[propertyName] = exp;
@@ -120,7 +129,7 @@ export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = '
         }
     }
 
-    function onParametersChange (parameter: string, value: string | number | boolean | any, pathParameter?: boolean, newRoute?: RouteToCreate)   {
+    function onParametersChange(parameter: string, value: string | number | boolean | any, pathParameter?: boolean, newRoute?: RouteToCreate) {
         value = value === '' ? undefined : value;
         if (selectedStep) {
             const clone = (CamelUtil.cloneStep(selectedStep));
@@ -143,11 +152,28 @@ export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = '
         }
     }
 
-    function cloneElement ()   {
+    function cloneElement() {
         // TODO:
     }
 
-    const convertStep = (step: CamelElement, targetDslName: string ) => {
+    function saveAsRoute(step: CamelElement, stepsOnly: boolean) {
+        if (step && step.hasSteps()) {
+            const stepClone = CamelUtil.cloneStep(step, true);
+            const from = CamelDefinitionApi.createFromDefinition({uri: "direct", parameters: {name: (step as any).id}});
+            if (stepsOnly) {
+                from.steps = (stepClone as any).steps;
+            } else {
+                from.steps = [stepClone]
+            }
+            const route = CamelDefinitionApi.createRouteDefinition({from: from, nodePrefixId: (step as any).id});
+            const clone = CamelUtil.cloneIntegration(integration);
+            clone.spec.flows?.push(route)
+            setIntegration(clone, false);
+            // setSelectedStep(element);
+        }
+    }
+
+    const convertStep = (step: CamelElement, targetDslName: string) => {
         try {
             // setSelectedStep(undefined);
             if (targetDslName === 'ChoiceDefinition' && step.dslName === 'FilterDefinition') {
@@ -156,7 +182,11 @@ export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = '
                 delete (clone as any).stepName;
                 const when = CamelDefinitionApi.createWhenDefinition(clone);
                 const otherwise = CamelDefinitionApi.createOtherwiseDefinition(undefined);
-                const choice = CamelDefinitionApi.createChoiceDefinition({uuid: step.uuid, when: [when], otherwise: otherwise});
+                const choice = CamelDefinitionApi.createChoiceDefinition({
+                    uuid: step.uuid,
+                    when: [when],
+                    otherwise: otherwise
+                });
                 onPropertyUpdate(choice);
                 setSelectedStep(choice);
             } else {
@@ -171,7 +201,7 @@ export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = '
                 })
                 delete (clone as any).dslName;
                 delete (clone as any).stepName;
-                const converted= CamelDefinitionApi.createStep(targetDslName, clone, true);
+                const converted = CamelDefinitionApi.createStep(targetDslName, clone, true);
                 onPropertyUpdate(converted);
                 setSelectedStep(converted);
             }
@@ -180,5 +210,14 @@ export function usePropertiesHook (designerType: 'routes' | 'rest' | 'beans' = '
         }
     }
 
-    return {convertStep, cloneElement, onPropertyChange, onParametersChange, onDataFormatChange, onExpressionChange, getInternalComponentName}
+    return {
+        saveAsRoute,
+        convertStep,
+        cloneElement,
+        onPropertyChange,
+        onParametersChange,
+        onDataFormatChange,
+        onExpressionChange,
+        getInternalComponentName
+    }
 }
\ No newline at end of file
diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/useRouteDesignerHook.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/route/useRouteDesignerHook.tsx
index 192988ab..543f2a00 100644
--- a/karavan-web/karavan-app/src/main/webui/src/designer/route/useRouteDesignerHook.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/useRouteDesignerHook.tsx
@@ -220,7 +220,7 @@ export function useRouteDesignerHook () {
         }
     }
 
-    const openSelector = (parentId: string | undefined, parentDsl: string | undefined, showSteps: boolean = true, position?: number | undefined, selectorTabIndex?: string | number) => {
+    const openSelector = (parentId: string | undefined, parentDsl: string | undefined, showSteps: boolean = true, position?: number | undefined) => {
         setShowSelector(true);
         setParentId(parentId || '');
         setParentDsl(parentDsl);