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/06 17:24:03 UTC

[camel-karavan] branch main updated: Fix #932

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 6682daf4 Fix #932
6682daf4 is described below

commit 6682daf4e7faeeea97372b6f86f16cbd8b9602cc
Author: Marat Gubaidullin <ma...@talismancloud.io>
AuthorDate: Fri Oct 6 13:23:51 2023 -0400

    Fix #932
---
 karavan-designer/src/App.tsx                       |  4 +-
 karavan-designer/src/designer/DesignerStore.ts     |  7 ++
 karavan-designer/src/designer/KaravanDesigner.tsx  |  3 +-
 karavan-designer/src/designer/route/DslElement.tsx | 45 ++----------
 .../src/designer/route/DslElementMoveModal.tsx     | 81 ++++++++++++++++++++++
 .../src/designer/route/RouteDesigner.tsx           |  7 +-
 karavan-space/src/designer/DesignerStore.ts        |  7 ++
 karavan-space/src/designer/KaravanDesigner.tsx     |  3 +-
 karavan-space/src/designer/rest/rest.css           |  5 +-
 karavan-space/src/designer/route/DslElement.tsx    | 45 ++----------
 .../src/designer/route/DslElementMoveModal.tsx     | 81 ++++++++++++++++++++++
 karavan-space/src/designer/route/DslProperties.tsx |  3 +-
 karavan-space/src/designer/route/RouteDesigner.tsx |  7 +-
 .../src/main/webui/src/designer/DesignerStore.ts   |  7 ++
 .../main/webui/src/designer/KaravanDesigner.tsx    |  3 +-
 .../main/webui/src/designer/route/DslElement.tsx   | 45 ++----------
 .../src/designer/route/DslElementMoveModal.tsx     | 81 ++++++++++++++++++++++
 .../webui/src/designer/route/DslProperties.tsx     |  3 +-
 .../webui/src/designer/route/RouteDesigner.tsx     |  7 +-
 19 files changed, 304 insertions(+), 140 deletions(-)

diff --git a/karavan-designer/src/App.tsx b/karavan-designer/src/App.tsx
index f23eb43d..9e760223 100644
--- a/karavan-designer/src/App.tsx
+++ b/karavan-designer/src/App.tsx
@@ -69,8 +69,8 @@ class App extends React.Component<Props, State> {
             fetch("components/components.json"),
             fetch("snippets/org.apache.camel.AggregationStrategy"),
             fetch("snippets/org.apache.camel.Processor"),
-            // fetch("example/demo.camel.yaml")
-            fetch("example/aws-s3-cdc-source.kamelet.yaml")
+            fetch("example/demo.camel.yaml")
+            // fetch("example/aws-s3-cdc-source.kamelet.yaml")
             // fetch("components/supported-components.json"),
         ]).then(responses =>
             Promise.all(responses.map(response => response.text()))
diff --git a/karavan-designer/src/designer/DesignerStore.ts b/karavan-designer/src/designer/DesignerStore.ts
index 15a9b7be..4831855f 100644
--- a/karavan-designer/src/designer/DesignerStore.ts
+++ b/karavan-designer/src/designer/DesignerStore.ts
@@ -166,7 +166,9 @@ type DesignerState = {
     height: number,
     top: number,
     left: number,
+    moveElements: [string | undefined, string | undefined]
 }
+
 const designerState: DesignerState = {
     notificationBadge: false,
     notificationMessage: ['', ''],
@@ -182,6 +184,7 @@ const designerState: DesignerState = {
     height: 0,
     top: 0,
     left: 0,
+    moveElements: [undefined, undefined]
 };
 
 type DesignerAction = {
@@ -197,6 +200,7 @@ type DesignerAction = {
     setPosition: (width: number, height: number, top: number, left: number) => void;
     reset: () => void;
     setNotification: (notificationBadge: boolean, notificationMessage: [string, string]) => void;
+    setMoveElements: (moveElements: [string | undefined, string | undefined]) => void;
 }
 
 export const useDesignerStore = createWithEqualityFn<DesignerState & DesignerAction>((set) => ({
@@ -248,5 +252,8 @@ export const useDesignerStore = createWithEqualityFn<DesignerState & DesignerAct
     },
     setNotification: (notificationBadge: boolean, notificationMessage: [string, string]) => {
         set({notificationBadge: notificationBadge, notificationMessage: notificationMessage})
+    },
+    setMoveElements: (moveElements: [string | undefined, string | undefined]) => {
+        set({moveElements: moveElements})
     }
 }), shallow)
\ No newline at end of file
diff --git a/karavan-designer/src/designer/KaravanDesigner.tsx b/karavan-designer/src/designer/KaravanDesigner.tsx
index 71914986..766b4e1f 100644
--- a/karavan-designer/src/designer/KaravanDesigner.tsx
+++ b/karavan-designer/src/designer/KaravanDesigner.tsx
@@ -134,7 +134,8 @@ export function KaravanDesigner(props: Props) {
     const isKamelet = integration.type === 'kamelet';
 
     return (
-        <PageSection variant={props.dark ? PageSectionVariants.darker : PageSectionVariants.light} className="page"
+        <PageSection variant={props.dark ? PageSectionVariants.darker : PageSectionVariants.light}
+                     className="page"
                      isFilled padding={{default: 'noPadding'}}>
             <div className={"main-tabs-wrapper"}>
                 <Tabs className="main-tabs"
diff --git a/karavan-designer/src/designer/route/DslElement.tsx b/karavan-designer/src/designer/route/DslElement.tsx
index 5f2744c5..0d246b20 100644
--- a/karavan-designer/src/designer/route/DslElement.tsx
+++ b/karavan-designer/src/designer/route/DslElement.tsx
@@ -16,9 +16,6 @@
  */
 import React, {CSSProperties, useMemo, useState} from 'react';
 import {
-    Button,
-    Flex,
-    Modal, ModalVariant,
     Text, Tooltip,
 } from '@patternfly/react-core';
 import '../karavan.css';
@@ -49,13 +46,12 @@ export function DslElement(props: Props) {
 
     const [integration] = useIntegrationStore((s) => [s.integration, s.setIntegration], shallow)
 
-    const [selectedUuids, selectedStep, showMoveConfirmation, setShowMoveConfirmation, hideLogDSL] =
+    const [selectedUuids, setShowMoveConfirmation, hideLogDSL, setMoveElements] =
         useDesignerStore((s) =>
-            [s.selectedUuids, s.selectedStep, s.showMoveConfirmation, s.setShowMoveConfirmation, s.hideLogDSL], shallow)
+            [s.selectedUuids, s.setShowMoveConfirmation, s.hideLogDSL, s.setMoveElements], shallow)
     const [isDragging, setIsDragging] = useState<boolean>(false);
 
     const [isDraggedOver, setIsDraggedOver] = useState<boolean>(false);
-    const [moveElements, setMoveElements] = useState<[string | undefined, string | undefined]>([undefined, undefined]);
 
     function onOpenSelector(evt: React.MouseEvent, showSteps: boolean = true, isInsert: boolean = false) {
         evt.stopPropagation();
@@ -92,20 +88,6 @@ export function DslElement(props: Props) {
         }
     }
 
-    function confirmMove(asChild: boolean) {
-        const sourceUuid = moveElements[0];
-        const targetUuid = moveElements[1];
-        if (sourceUuid && targetUuid && sourceUuid !== targetUuid) {
-            moveElement(sourceUuid, targetUuid, asChild);
-            cancelMove();
-        }
-    }
-
-    function cancelMove() {
-        setShowMoveConfirmation(false);
-        setMoveElements([undefined, undefined]);
-    }
-
     function isElementSelected(): boolean {
         return selectedUuids.includes(props.step.uuid);
     }
@@ -388,7 +370,8 @@ export function DslElement(props: Props) {
 
     function getAddElementButton() {
         return (
-            <Tooltip position={"bottom"} content={<div>{"Add DSL element to " + CamelDisplayUtil.getTitle(props.step)}</div>}>
+            <Tooltip position={"bottom"}
+                     content={<div>{"Add DSL element to " + CamelDisplayUtil.getTitle(props.step)}</div>}>
                 <button
                     type="button"
                     aria-label="Add"
@@ -419,25 +402,6 @@ export function DslElement(props: Props) {
         )
     }
 
-    function getMoveConfirmation() {
-        return (
-            <Modal
-                aria-label="title"
-                className='move-modal'
-                isOpen={showMoveConfirmation}
-                variant={ModalVariant.small}
-            ><Flex direction={{default: "column"}}>
-                <div>Select move type:</div>
-                <Button key="place" variant="primary" onClick={event => confirmMove(false)}>Shift (target down)</Button>
-                <Button key="child" variant="secondary" onClick={event => confirmMove(true)}>Move as target
-                    step</Button>
-                <Button key="cancel" variant="tertiary" onClick={event => cancelMove()}>Cancel</Button>
-            </Flex>
-
-            </Modal>
-        )
-    }
-
     const element: CamelElement = props.step;
     const className = "step-element" + (isElementSelected() ? " step-element-selected" : "") + (!props.step.showChildren ? " hidden-step" : "");
     return (
@@ -487,7 +451,6 @@ export function DslElement(props: Props) {
         >
             {getElementHeader()}
             {getChildElements()}
-            {getMoveConfirmation()}
         </div>
     )
 }
diff --git a/karavan-designer/src/designer/route/DslElementMoveModal.tsx b/karavan-designer/src/designer/route/DslElementMoveModal.tsx
new file mode 100644
index 00000000..d1e17ecf
--- /dev/null
+++ b/karavan-designer/src/designer/route/DslElementMoveModal.tsx
@@ -0,0 +1,81 @@
+/*
+ * 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 {
+    Button,
+    Flex,
+    Modal, ModalVariant,
+} from '@patternfly/react-core';
+import '../karavan.css';
+import {useDesignerStore, useIntegrationStore} from "../DesignerStore";
+import {shallow} from "zustand/shallow";
+import {useRouteDesignerHook} from "./useRouteDesignerHook";
+import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
+
+export function DslElementMoveModal() {
+
+    const {moveElement} = useRouteDesignerHook();
+    const [integration] = useIntegrationStore((s) => [s.integration, s.setIntegration], shallow)
+    const [ showMoveConfirmation, setShowMoveConfirmation, moveElements, setMoveElements] =
+        useDesignerStore((s) =>
+            [s.showMoveConfirmation, s.setShowMoveConfirmation, s.moveElements, s.setMoveElements], shallow)
+
+    function confirmMove(asChild: boolean) {
+        const sourceUuid = moveElements[0];
+        const targetUuid = moveElements[1];
+        if (sourceUuid && targetUuid && sourceUuid !== targetUuid) {
+            moveElement(sourceUuid, targetUuid, asChild);
+            cancelMove();
+        }
+    }
+
+    function cancelMove() {
+        setShowMoveConfirmation(false);
+        setMoveElements([undefined, undefined]);
+    }
+
+    function canReplace() {
+        const targetUuid = moveElements[1];
+        if (targetUuid) {
+            const targetElement = CamelDefinitionApiExt.findElementInIntegration(integration, targetUuid);
+            if (targetElement) {
+                return  !['WhenDefinition', 'OtherwiseDefinition'].includes(targetElement?.dslName);
+            }
+        }
+        return true;
+    }
+
+    return (
+        <Modal
+            aria-label="title"
+            className='move-modal'
+            isOpen={showMoveConfirmation}
+            onClose={event => cancelMove()}
+            variant={ModalVariant.small}
+        >
+            <Flex direction={{default: "column"}}>
+                <div>Select move type:</div>
+                {canReplace() && <Button key="place" variant="primary" onClick={event => confirmMove(false)}
+                >
+                    Replace (target down)
+                </Button>}
+                <Button key="child" variant="secondary" onClick={event => confirmMove(true)}>Set as child</Button>
+                <Button key="cancel" variant="tertiary" onClick={event => cancelMove()}>Cancel</Button>
+            </Flex>
+        </Modal>
+    )
+}
diff --git a/karavan-designer/src/designer/route/RouteDesigner.tsx b/karavan-designer/src/designer/route/RouteDesigner.tsx
index 7d3b5950..215f334b 100644
--- a/karavan-designer/src/designer/route/RouteDesigner.tsx
+++ b/karavan-designer/src/designer/route/RouteDesigner.tsx
@@ -36,14 +36,16 @@ import useResizeObserver from "./useResizeObserver";
 import {Command, EventBus} from "../utils/EventBus";
 import useMutationsObserver from "./useDrawerMutationsObserver";
 import {DeleteConfirmation} from "./DeleteConfirmation";
+import {DslElementMoveModal} from "./DslElementMoveModal";
 
 export function RouteDesigner() {
 
     const {openSelector, createRouteConfiguration, onCommand, handleKeyDown, handleKeyUp, unselectElement} = useRouteDesignerHook();
 
     const [integration] = useIntegrationStore((state) => [state.integration], shallow)
-    const [showDeleteConfirmation, setPosition, width, height, top, left, hideLogDSL] = useDesignerStore((s) =>
-        [s.showDeleteConfirmation, s.setPosition, s.width, s.height, s.top, s.left, s.hideLogDSL], shallow)
+    const [showDeleteConfirmation, setPosition, width, height, top, left, hideLogDSL, showMoveConfirmation, setShowMoveConfirmation] =
+        useDesignerStore((s) =>
+        [s.showDeleteConfirmation, s.setPosition, s.width, s.height, s.top, s.left, s.hideLogDSL, s.showMoveConfirmation, s.setShowMoveConfirmation], shallow)
 
     const [showSelector] = useSelectorStore((s) => [s.showSelector], shallow)
 
@@ -161,6 +163,7 @@ export function RouteDesigner() {
             </div>
             {showSelector && <DslSelector/>}
             {showDeleteConfirmation && <DeleteConfirmation/>}
+            {showMoveConfirmation && <DslElementMoveModal/>}
         </div>
     )
 }
\ No newline at end of file
diff --git a/karavan-space/src/designer/DesignerStore.ts b/karavan-space/src/designer/DesignerStore.ts
index 15a9b7be..4831855f 100644
--- a/karavan-space/src/designer/DesignerStore.ts
+++ b/karavan-space/src/designer/DesignerStore.ts
@@ -166,7 +166,9 @@ type DesignerState = {
     height: number,
     top: number,
     left: number,
+    moveElements: [string | undefined, string | undefined]
 }
+
 const designerState: DesignerState = {
     notificationBadge: false,
     notificationMessage: ['', ''],
@@ -182,6 +184,7 @@ const designerState: DesignerState = {
     height: 0,
     top: 0,
     left: 0,
+    moveElements: [undefined, undefined]
 };
 
 type DesignerAction = {
@@ -197,6 +200,7 @@ type DesignerAction = {
     setPosition: (width: number, height: number, top: number, left: number) => void;
     reset: () => void;
     setNotification: (notificationBadge: boolean, notificationMessage: [string, string]) => void;
+    setMoveElements: (moveElements: [string | undefined, string | undefined]) => void;
 }
 
 export const useDesignerStore = createWithEqualityFn<DesignerState & DesignerAction>((set) => ({
@@ -248,5 +252,8 @@ export const useDesignerStore = createWithEqualityFn<DesignerState & DesignerAct
     },
     setNotification: (notificationBadge: boolean, notificationMessage: [string, string]) => {
         set({notificationBadge: notificationBadge, notificationMessage: notificationMessage})
+    },
+    setMoveElements: (moveElements: [string | undefined, string | undefined]) => {
+        set({moveElements: moveElements})
     }
 }), shallow)
\ No newline at end of file
diff --git a/karavan-space/src/designer/KaravanDesigner.tsx b/karavan-space/src/designer/KaravanDesigner.tsx
index 71914986..766b4e1f 100644
--- a/karavan-space/src/designer/KaravanDesigner.tsx
+++ b/karavan-space/src/designer/KaravanDesigner.tsx
@@ -134,7 +134,8 @@ export function KaravanDesigner(props: Props) {
     const isKamelet = integration.type === 'kamelet';
 
     return (
-        <PageSection variant={props.dark ? PageSectionVariants.darker : PageSectionVariants.light} className="page"
+        <PageSection variant={props.dark ? PageSectionVariants.darker : PageSectionVariants.light}
+                     className="page"
                      isFilled padding={{default: 'noPadding'}}>
             <div className={"main-tabs-wrapper"}>
                 <Tabs className="main-tabs"
diff --git a/karavan-space/src/designer/rest/rest.css b/karavan-space/src/designer/rest/rest.css
index 25012e2d..3f1a53a5 100644
--- a/karavan-space/src/designer/rest/rest.css
+++ b/karavan-space/src/designer/rest/rest.css
@@ -185,6 +185,7 @@
     margin-left: 6px;
     cursor: pointer;
     justify-content: space-between;
+    position: relative;
 }
 
 .karavan .rest-designer .rest-config-card,
@@ -238,8 +239,8 @@
 .karavan .rest-designer .rest-card .delete-button,
 .karavan .rest-designer .method-card .delete-button {
     position: absolute;
-    top: 3px;
-    right: 3px;
+    top: -7px;
+    right: -7px;
     line-height: 1;
     border: 0;
     padding: 0;
diff --git a/karavan-space/src/designer/route/DslElement.tsx b/karavan-space/src/designer/route/DslElement.tsx
index 5f2744c5..0d246b20 100644
--- a/karavan-space/src/designer/route/DslElement.tsx
+++ b/karavan-space/src/designer/route/DslElement.tsx
@@ -16,9 +16,6 @@
  */
 import React, {CSSProperties, useMemo, useState} from 'react';
 import {
-    Button,
-    Flex,
-    Modal, ModalVariant,
     Text, Tooltip,
 } from '@patternfly/react-core';
 import '../karavan.css';
@@ -49,13 +46,12 @@ export function DslElement(props: Props) {
 
     const [integration] = useIntegrationStore((s) => [s.integration, s.setIntegration], shallow)
 
-    const [selectedUuids, selectedStep, showMoveConfirmation, setShowMoveConfirmation, hideLogDSL] =
+    const [selectedUuids, setShowMoveConfirmation, hideLogDSL, setMoveElements] =
         useDesignerStore((s) =>
-            [s.selectedUuids, s.selectedStep, s.showMoveConfirmation, s.setShowMoveConfirmation, s.hideLogDSL], shallow)
+            [s.selectedUuids, s.setShowMoveConfirmation, s.hideLogDSL, s.setMoveElements], shallow)
     const [isDragging, setIsDragging] = useState<boolean>(false);
 
     const [isDraggedOver, setIsDraggedOver] = useState<boolean>(false);
-    const [moveElements, setMoveElements] = useState<[string | undefined, string | undefined]>([undefined, undefined]);
 
     function onOpenSelector(evt: React.MouseEvent, showSteps: boolean = true, isInsert: boolean = false) {
         evt.stopPropagation();
@@ -92,20 +88,6 @@ export function DslElement(props: Props) {
         }
     }
 
-    function confirmMove(asChild: boolean) {
-        const sourceUuid = moveElements[0];
-        const targetUuid = moveElements[1];
-        if (sourceUuid && targetUuid && sourceUuid !== targetUuid) {
-            moveElement(sourceUuid, targetUuid, asChild);
-            cancelMove();
-        }
-    }
-
-    function cancelMove() {
-        setShowMoveConfirmation(false);
-        setMoveElements([undefined, undefined]);
-    }
-
     function isElementSelected(): boolean {
         return selectedUuids.includes(props.step.uuid);
     }
@@ -388,7 +370,8 @@ export function DslElement(props: Props) {
 
     function getAddElementButton() {
         return (
-            <Tooltip position={"bottom"} content={<div>{"Add DSL element to " + CamelDisplayUtil.getTitle(props.step)}</div>}>
+            <Tooltip position={"bottom"}
+                     content={<div>{"Add DSL element to " + CamelDisplayUtil.getTitle(props.step)}</div>}>
                 <button
                     type="button"
                     aria-label="Add"
@@ -419,25 +402,6 @@ export function DslElement(props: Props) {
         )
     }
 
-    function getMoveConfirmation() {
-        return (
-            <Modal
-                aria-label="title"
-                className='move-modal'
-                isOpen={showMoveConfirmation}
-                variant={ModalVariant.small}
-            ><Flex direction={{default: "column"}}>
-                <div>Select move type:</div>
-                <Button key="place" variant="primary" onClick={event => confirmMove(false)}>Shift (target down)</Button>
-                <Button key="child" variant="secondary" onClick={event => confirmMove(true)}>Move as target
-                    step</Button>
-                <Button key="cancel" variant="tertiary" onClick={event => cancelMove()}>Cancel</Button>
-            </Flex>
-
-            </Modal>
-        )
-    }
-
     const element: CamelElement = props.step;
     const className = "step-element" + (isElementSelected() ? " step-element-selected" : "") + (!props.step.showChildren ? " hidden-step" : "");
     return (
@@ -487,7 +451,6 @@ export function DslElement(props: Props) {
         >
             {getElementHeader()}
             {getChildElements()}
-            {getMoveConfirmation()}
         </div>
     )
 }
diff --git a/karavan-space/src/designer/route/DslElementMoveModal.tsx b/karavan-space/src/designer/route/DslElementMoveModal.tsx
new file mode 100644
index 00000000..d1e17ecf
--- /dev/null
+++ b/karavan-space/src/designer/route/DslElementMoveModal.tsx
@@ -0,0 +1,81 @@
+/*
+ * 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 {
+    Button,
+    Flex,
+    Modal, ModalVariant,
+} from '@patternfly/react-core';
+import '../karavan.css';
+import {useDesignerStore, useIntegrationStore} from "../DesignerStore";
+import {shallow} from "zustand/shallow";
+import {useRouteDesignerHook} from "./useRouteDesignerHook";
+import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
+
+export function DslElementMoveModal() {
+
+    const {moveElement} = useRouteDesignerHook();
+    const [integration] = useIntegrationStore((s) => [s.integration, s.setIntegration], shallow)
+    const [ showMoveConfirmation, setShowMoveConfirmation, moveElements, setMoveElements] =
+        useDesignerStore((s) =>
+            [s.showMoveConfirmation, s.setShowMoveConfirmation, s.moveElements, s.setMoveElements], shallow)
+
+    function confirmMove(asChild: boolean) {
+        const sourceUuid = moveElements[0];
+        const targetUuid = moveElements[1];
+        if (sourceUuid && targetUuid && sourceUuid !== targetUuid) {
+            moveElement(sourceUuid, targetUuid, asChild);
+            cancelMove();
+        }
+    }
+
+    function cancelMove() {
+        setShowMoveConfirmation(false);
+        setMoveElements([undefined, undefined]);
+    }
+
+    function canReplace() {
+        const targetUuid = moveElements[1];
+        if (targetUuid) {
+            const targetElement = CamelDefinitionApiExt.findElementInIntegration(integration, targetUuid);
+            if (targetElement) {
+                return  !['WhenDefinition', 'OtherwiseDefinition'].includes(targetElement?.dslName);
+            }
+        }
+        return true;
+    }
+
+    return (
+        <Modal
+            aria-label="title"
+            className='move-modal'
+            isOpen={showMoveConfirmation}
+            onClose={event => cancelMove()}
+            variant={ModalVariant.small}
+        >
+            <Flex direction={{default: "column"}}>
+                <div>Select move type:</div>
+                {canReplace() && <Button key="place" variant="primary" onClick={event => confirmMove(false)}
+                >
+                    Replace (target down)
+                </Button>}
+                <Button key="child" variant="secondary" onClick={event => confirmMove(true)}>Set as child</Button>
+                <Button key="cancel" variant="tertiary" onClick={event => cancelMove()}>Cancel</Button>
+            </Flex>
+        </Modal>
+    )
+}
diff --git a/karavan-space/src/designer/route/DslProperties.tsx b/karavan-space/src/designer/route/DslProperties.tsx
index 82fd3dd9..8231759c 100644
--- a/karavan-space/src/designer/route/DslProperties.tsx
+++ b/karavan-space/src/designer/route/DslProperties.tsx
@@ -41,8 +41,7 @@ interface Props {
 
 export function DslProperties(props: Props) {
 
-    const [integration, setIntegration] = useIntegrationStore((state) =>
-        [state.integration, state.setIntegration], shallow)
+    const [integration] = useIntegrationStore((state) => [state.integration], shallow)
 
     const {cloneElement, onDataFormatChange, onPropertyChange, onParametersChange, onExpressionChange} = usePropertiesHook(props.isRouteDesigner);
 
diff --git a/karavan-space/src/designer/route/RouteDesigner.tsx b/karavan-space/src/designer/route/RouteDesigner.tsx
index 7d3b5950..215f334b 100644
--- a/karavan-space/src/designer/route/RouteDesigner.tsx
+++ b/karavan-space/src/designer/route/RouteDesigner.tsx
@@ -36,14 +36,16 @@ import useResizeObserver from "./useResizeObserver";
 import {Command, EventBus} from "../utils/EventBus";
 import useMutationsObserver from "./useDrawerMutationsObserver";
 import {DeleteConfirmation} from "./DeleteConfirmation";
+import {DslElementMoveModal} from "./DslElementMoveModal";
 
 export function RouteDesigner() {
 
     const {openSelector, createRouteConfiguration, onCommand, handleKeyDown, handleKeyUp, unselectElement} = useRouteDesignerHook();
 
     const [integration] = useIntegrationStore((state) => [state.integration], shallow)
-    const [showDeleteConfirmation, setPosition, width, height, top, left, hideLogDSL] = useDesignerStore((s) =>
-        [s.showDeleteConfirmation, s.setPosition, s.width, s.height, s.top, s.left, s.hideLogDSL], shallow)
+    const [showDeleteConfirmation, setPosition, width, height, top, left, hideLogDSL, showMoveConfirmation, setShowMoveConfirmation] =
+        useDesignerStore((s) =>
+        [s.showDeleteConfirmation, s.setPosition, s.width, s.height, s.top, s.left, s.hideLogDSL, s.showMoveConfirmation, s.setShowMoveConfirmation], shallow)
 
     const [showSelector] = useSelectorStore((s) => [s.showSelector], shallow)
 
@@ -161,6 +163,7 @@ export function RouteDesigner() {
             </div>
             {showSelector && <DslSelector/>}
             {showDeleteConfirmation && <DeleteConfirmation/>}
+            {showMoveConfirmation && <DslElementMoveModal/>}
         </div>
     )
 }
\ No newline at end of file
diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/DesignerStore.ts b/karavan-web/karavan-app/src/main/webui/src/designer/DesignerStore.ts
index 15a9b7be..4831855f 100644
--- a/karavan-web/karavan-app/src/main/webui/src/designer/DesignerStore.ts
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/DesignerStore.ts
@@ -166,7 +166,9 @@ type DesignerState = {
     height: number,
     top: number,
     left: number,
+    moveElements: [string | undefined, string | undefined]
 }
+
 const designerState: DesignerState = {
     notificationBadge: false,
     notificationMessage: ['', ''],
@@ -182,6 +184,7 @@ const designerState: DesignerState = {
     height: 0,
     top: 0,
     left: 0,
+    moveElements: [undefined, undefined]
 };
 
 type DesignerAction = {
@@ -197,6 +200,7 @@ type DesignerAction = {
     setPosition: (width: number, height: number, top: number, left: number) => void;
     reset: () => void;
     setNotification: (notificationBadge: boolean, notificationMessage: [string, string]) => void;
+    setMoveElements: (moveElements: [string | undefined, string | undefined]) => void;
 }
 
 export const useDesignerStore = createWithEqualityFn<DesignerState & DesignerAction>((set) => ({
@@ -248,5 +252,8 @@ export const useDesignerStore = createWithEqualityFn<DesignerState & DesignerAct
     },
     setNotification: (notificationBadge: boolean, notificationMessage: [string, string]) => {
         set({notificationBadge: notificationBadge, notificationMessage: notificationMessage})
+    },
+    setMoveElements: (moveElements: [string | undefined, string | undefined]) => {
+        set({moveElements: moveElements})
     }
 }), shallow)
\ No newline at end of file
diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx
index 71914986..766b4e1f 100644
--- a/karavan-web/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx
@@ -134,7 +134,8 @@ export function KaravanDesigner(props: Props) {
     const isKamelet = integration.type === 'kamelet';
 
     return (
-        <PageSection variant={props.dark ? PageSectionVariants.darker : PageSectionVariants.light} className="page"
+        <PageSection variant={props.dark ? PageSectionVariants.darker : PageSectionVariants.light}
+                     className="page"
                      isFilled padding={{default: 'noPadding'}}>
             <div className={"main-tabs-wrapper"}>
                 <Tabs className="main-tabs"
diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/DslElement.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/route/DslElement.tsx
index 5f2744c5..0d246b20 100644
--- a/karavan-web/karavan-app/src/main/webui/src/designer/route/DslElement.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/DslElement.tsx
@@ -16,9 +16,6 @@
  */
 import React, {CSSProperties, useMemo, useState} from 'react';
 import {
-    Button,
-    Flex,
-    Modal, ModalVariant,
     Text, Tooltip,
 } from '@patternfly/react-core';
 import '../karavan.css';
@@ -49,13 +46,12 @@ export function DslElement(props: Props) {
 
     const [integration] = useIntegrationStore((s) => [s.integration, s.setIntegration], shallow)
 
-    const [selectedUuids, selectedStep, showMoveConfirmation, setShowMoveConfirmation, hideLogDSL] =
+    const [selectedUuids, setShowMoveConfirmation, hideLogDSL, setMoveElements] =
         useDesignerStore((s) =>
-            [s.selectedUuids, s.selectedStep, s.showMoveConfirmation, s.setShowMoveConfirmation, s.hideLogDSL], shallow)
+            [s.selectedUuids, s.setShowMoveConfirmation, s.hideLogDSL, s.setMoveElements], shallow)
     const [isDragging, setIsDragging] = useState<boolean>(false);
 
     const [isDraggedOver, setIsDraggedOver] = useState<boolean>(false);
-    const [moveElements, setMoveElements] = useState<[string | undefined, string | undefined]>([undefined, undefined]);
 
     function onOpenSelector(evt: React.MouseEvent, showSteps: boolean = true, isInsert: boolean = false) {
         evt.stopPropagation();
@@ -92,20 +88,6 @@ export function DslElement(props: Props) {
         }
     }
 
-    function confirmMove(asChild: boolean) {
-        const sourceUuid = moveElements[0];
-        const targetUuid = moveElements[1];
-        if (sourceUuid && targetUuid && sourceUuid !== targetUuid) {
-            moveElement(sourceUuid, targetUuid, asChild);
-            cancelMove();
-        }
-    }
-
-    function cancelMove() {
-        setShowMoveConfirmation(false);
-        setMoveElements([undefined, undefined]);
-    }
-
     function isElementSelected(): boolean {
         return selectedUuids.includes(props.step.uuid);
     }
@@ -388,7 +370,8 @@ export function DslElement(props: Props) {
 
     function getAddElementButton() {
         return (
-            <Tooltip position={"bottom"} content={<div>{"Add DSL element to " + CamelDisplayUtil.getTitle(props.step)}</div>}>
+            <Tooltip position={"bottom"}
+                     content={<div>{"Add DSL element to " + CamelDisplayUtil.getTitle(props.step)}</div>}>
                 <button
                     type="button"
                     aria-label="Add"
@@ -419,25 +402,6 @@ export function DslElement(props: Props) {
         )
     }
 
-    function getMoveConfirmation() {
-        return (
-            <Modal
-                aria-label="title"
-                className='move-modal'
-                isOpen={showMoveConfirmation}
-                variant={ModalVariant.small}
-            ><Flex direction={{default: "column"}}>
-                <div>Select move type:</div>
-                <Button key="place" variant="primary" onClick={event => confirmMove(false)}>Shift (target down)</Button>
-                <Button key="child" variant="secondary" onClick={event => confirmMove(true)}>Move as target
-                    step</Button>
-                <Button key="cancel" variant="tertiary" onClick={event => cancelMove()}>Cancel</Button>
-            </Flex>
-
-            </Modal>
-        )
-    }
-
     const element: CamelElement = props.step;
     const className = "step-element" + (isElementSelected() ? " step-element-selected" : "") + (!props.step.showChildren ? " hidden-step" : "");
     return (
@@ -487,7 +451,6 @@ export function DslElement(props: Props) {
         >
             {getElementHeader()}
             {getChildElements()}
-            {getMoveConfirmation()}
         </div>
     )
 }
diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/DslElementMoveModal.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/route/DslElementMoveModal.tsx
new file mode 100644
index 00000000..d1e17ecf
--- /dev/null
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/DslElementMoveModal.tsx
@@ -0,0 +1,81 @@
+/*
+ * 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 {
+    Button,
+    Flex,
+    Modal, ModalVariant,
+} from '@patternfly/react-core';
+import '../karavan.css';
+import {useDesignerStore, useIntegrationStore} from "../DesignerStore";
+import {shallow} from "zustand/shallow";
+import {useRouteDesignerHook} from "./useRouteDesignerHook";
+import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
+
+export function DslElementMoveModal() {
+
+    const {moveElement} = useRouteDesignerHook();
+    const [integration] = useIntegrationStore((s) => [s.integration, s.setIntegration], shallow)
+    const [ showMoveConfirmation, setShowMoveConfirmation, moveElements, setMoveElements] =
+        useDesignerStore((s) =>
+            [s.showMoveConfirmation, s.setShowMoveConfirmation, s.moveElements, s.setMoveElements], shallow)
+
+    function confirmMove(asChild: boolean) {
+        const sourceUuid = moveElements[0];
+        const targetUuid = moveElements[1];
+        if (sourceUuid && targetUuid && sourceUuid !== targetUuid) {
+            moveElement(sourceUuid, targetUuid, asChild);
+            cancelMove();
+        }
+    }
+
+    function cancelMove() {
+        setShowMoveConfirmation(false);
+        setMoveElements([undefined, undefined]);
+    }
+
+    function canReplace() {
+        const targetUuid = moveElements[1];
+        if (targetUuid) {
+            const targetElement = CamelDefinitionApiExt.findElementInIntegration(integration, targetUuid);
+            if (targetElement) {
+                return  !['WhenDefinition', 'OtherwiseDefinition'].includes(targetElement?.dslName);
+            }
+        }
+        return true;
+    }
+
+    return (
+        <Modal
+            aria-label="title"
+            className='move-modal'
+            isOpen={showMoveConfirmation}
+            onClose={event => cancelMove()}
+            variant={ModalVariant.small}
+        >
+            <Flex direction={{default: "column"}}>
+                <div>Select move type:</div>
+                {canReplace() && <Button key="place" variant="primary" onClick={event => confirmMove(false)}
+                >
+                    Replace (target down)
+                </Button>}
+                <Button key="child" variant="secondary" onClick={event => confirmMove(true)}>Set as child</Button>
+                <Button key="cancel" variant="tertiary" onClick={event => cancelMove()}>Cancel</Button>
+            </Flex>
+        </Modal>
+    )
+}
diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/DslProperties.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/route/DslProperties.tsx
index 82fd3dd9..8231759c 100644
--- a/karavan-web/karavan-app/src/main/webui/src/designer/route/DslProperties.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/DslProperties.tsx
@@ -41,8 +41,7 @@ interface Props {
 
 export function DslProperties(props: Props) {
 
-    const [integration, setIntegration] = useIntegrationStore((state) =>
-        [state.integration, state.setIntegration], shallow)
+    const [integration] = useIntegrationStore((state) => [state.integration], shallow)
 
     const {cloneElement, onDataFormatChange, onPropertyChange, onParametersChange, onExpressionChange} = usePropertiesHook(props.isRouteDesigner);
 
diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/RouteDesigner.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/route/RouteDesigner.tsx
index 7d3b5950..215f334b 100644
--- a/karavan-web/karavan-app/src/main/webui/src/designer/route/RouteDesigner.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/RouteDesigner.tsx
@@ -36,14 +36,16 @@ import useResizeObserver from "./useResizeObserver";
 import {Command, EventBus} from "../utils/EventBus";
 import useMutationsObserver from "./useDrawerMutationsObserver";
 import {DeleteConfirmation} from "./DeleteConfirmation";
+import {DslElementMoveModal} from "./DslElementMoveModal";
 
 export function RouteDesigner() {
 
     const {openSelector, createRouteConfiguration, onCommand, handleKeyDown, handleKeyUp, unselectElement} = useRouteDesignerHook();
 
     const [integration] = useIntegrationStore((state) => [state.integration], shallow)
-    const [showDeleteConfirmation, setPosition, width, height, top, left, hideLogDSL] = useDesignerStore((s) =>
-        [s.showDeleteConfirmation, s.setPosition, s.width, s.height, s.top, s.left, s.hideLogDSL], shallow)
+    const [showDeleteConfirmation, setPosition, width, height, top, left, hideLogDSL, showMoveConfirmation, setShowMoveConfirmation] =
+        useDesignerStore((s) =>
+        [s.showDeleteConfirmation, s.setPosition, s.width, s.height, s.top, s.left, s.hideLogDSL, s.showMoveConfirmation, s.setShowMoveConfirmation], shallow)
 
     const [showSelector] = useSelectorStore((s) => [s.showSelector], shallow)
 
@@ -161,6 +163,7 @@ export function RouteDesigner() {
             </div>
             {showSelector && <DslSelector/>}
             {showDeleteConfirmation && <DeleteConfirmation/>}
+            {showMoveConfirmation && <DslElementMoveModal/>}
         </div>
     )
 }
\ No newline at end of file