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:14 UTC

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

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);