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/07 02:37:06 UTC

(camel-karavan) branch main updated (75c03d83 -> 133b002a)

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 75c03d83 Preview example of new Designer #1012
     new e1fcd92d Preview fixes of new Designer
     new 9d503ba5 Preview fixes of new Designer
     new 5eb8615b Preview fixes of new Designer
     new 133b002a Preview fixes of new Designer

The 4 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:
 karavan-designer/public/example/demo.camel.yaml    |  33 +++
 karavan-designer/src/designer/DesignerStore.ts     |  34 +--
 .../src/designer/route/DslConnections.tsx          | 200 ++++++++-------
 .../src/designer/route/RouteDesigner.tsx           |   6 +-
 .../src/designer/route/element/DslElement.css      |  30 ++-
 .../src/designer/route/element/DslElement.tsx      | 263 +++-----------------
 .../designer/route/element/DslElementHeader.tsx    | 275 +++++++++++++++++++++
 .../src/designer/route/useRouteDesignerHook.tsx    |   3 +-
 karavan-designer/src/designer/utils/CamelUi.tsx    |   3 +-
 karavan-designer/src/designer/utils/EventBus.ts    |  34 +--
 karavan-space/src/designer/DesignerStore.ts        |  34 +--
 .../src/designer/route/DslConnections.tsx          | 200 ++++++++-------
 karavan-space/src/designer/route/RouteDesigner.tsx |   6 +-
 .../src/designer/route/element/DslElement.css      |  30 ++-
 .../src/designer/route/element/DslElement.tsx      | 265 +++-----------------
 .../designer/route/element/DslElementHeader.tsx    | 275 +++++++++++++++++++++
 .../src/designer/route/useRouteDesignerHook.tsx    |   3 +-
 karavan-space/src/designer/utils/CamelUi.tsx       |   3 +-
 karavan-space/src/designer/utils/EventBus.ts       |  34 +--
 .../src/main/webui/src/designer/DesignerStore.ts   |  34 +--
 .../webui/src/designer/route/DslConnections.tsx    | 200 ++++++++-------
 .../webui/src/designer/route/RouteDesigner.tsx     |   6 +-
 .../src/designer/route/element/DslElement.css      |  30 ++-
 .../src/designer/route/element/DslElement.tsx      | 265 +++-----------------
 .../designer/route/element/DslElementHeader.tsx    | 275 +++++++++++++++++++++
 .../src/designer/route/useRouteDesignerHook.tsx    |   3 +-
 .../src/main/webui/src/designer/utils/CamelUi.tsx  |   3 +-
 .../src/main/webui/src/designer/utils/EventBus.ts  |  34 +--
 28 files changed, 1394 insertions(+), 1187 deletions(-)
 create mode 100644 karavan-designer/src/designer/route/element/DslElementHeader.tsx
 create mode 100644 karavan-space/src/designer/route/element/DslElementHeader.tsx
 create mode 100644 karavan-web/karavan-app/src/main/webui/src/designer/route/element/DslElementHeader.tsx


(camel-karavan) 01/04: Preview fixes of new Designer

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 e1fcd92dbc5fd548eb5de9aea2b4cce120eb1c82
Author: Marat Gubaidullin <ma...@talismancloud.io>
AuthorDate: Wed Dec 6 11:01:02 2023 -0500

    Preview fixes of new Designer
---
 karavan-designer/public/example/demo.camel.yaml    |  19 ++
 .../src/designer/route/DslConnections.tsx          |   4 +-
 .../src/designer/route/element/DslElement.css      |  17 +-
 .../src/designer/route/element/DslElement.tsx      | 213 ++-------------
 .../{DslElement.tsx => DslElementHeader.tsx}       | 304 +++------------------
 karavan-designer/src/designer/utils/CamelUi.tsx    |   3 +-
 6 files changed, 96 insertions(+), 464 deletions(-)

diff --git a/karavan-designer/public/example/demo.camel.yaml b/karavan-designer/public/example/demo.camel.yaml
index 40e3c45b..ccd98241 100644
--- a/karavan-designer/public/example/demo.camel.yaml
+++ b/karavan-designer/public/example/demo.camel.yaml
@@ -32,6 +32,25 @@
                     message: ${body}
                     id: log-6831
             id: choice-c1db
+        - saga:
+            id: saga-8f2c
+            steps:
+              - to:
+                  uri: kamelet:azure-cosmosdb-sink
+                  id: to-1394
+- route:
+    nodePrefixId: route-d10
+    id: route-3ad9
+    from:
+      uri: kamelet:azure-storage-datalake-source
+      id: from-1516
+- route:
+    nodePrefixId: route-171
+    id: route-99f9
+    from:
+      uri: kamelet:azure-storage-blob-source
+      id: from-f8e9
+      steps:
         - multicast:
             id: multicast-6a53
             steps:
diff --git a/karavan-designer/src/designer/route/DslConnections.tsx b/karavan-designer/src/designer/route/DslConnections.tsx
index 5f9467b6..a4eef251 100644
--- a/karavan-designer/src/designer/route/DslConnections.tsx
+++ b/karavan-designer/src/designer/route/DslConnections.tsx
@@ -283,7 +283,7 @@ export function DslConnections() {
             if (parent) {
             const rect1 = parent.headerRect;
             const rect2 = pos.headerRect;
-            return getComplexArrow(pos.step.uuid, rect1, rect2);
+            return getComplexArrow(pos.step.uuid + ":" + pos.parent.uuid, rect1, rect2);
             }
         }
     }
@@ -294,7 +294,7 @@ export function DslConnections() {
         const nextStep = steps.get(uuid);
         const rect2 = nextStep?.rect;
         if (rect1 && rect2) {
-            return getComplexArrow(uuid, rect1, rect2);
+            return getComplexArrow(uuid + "-" + btn.nextstep.uuid, rect1, rect2);
         }
     }
 
diff --git a/karavan-designer/src/designer/route/element/DslElement.css b/karavan-designer/src/designer/route/element/DslElement.css
index 3039f422..7af5bd51 100644
--- a/karavan-designer/src/designer/route/element/DslElement.css
+++ b/karavan-designer/src/designer/route/element/DslElement.css
@@ -17,12 +17,19 @@
 
 .karavan .dsl-page .flows .step-element .header-route {
     display: block;
-    border: none;
     background: transparent;
-    padding: 0;
-    margin: 3px 24px 10px 24px;
-    /*min-width: 260px;*/
+    padding: 10px;
+    margin: 0;
     z-index: 101;
+    min-width: 260px;
+}
+
+.karavan .dsl-page .flows .step-element .header-bottom-line {
+    border-bottom: 1px dashed;
+}
+
+.karavan .dsl-page .flows .step-element .header-route:hover {
+    cursor: pointer;
 }
 
 .karavan .step-element .header-route .delete-button {
@@ -214,4 +221,4 @@
     width: 20px;
     height: 20px;
     background: white;
-}
\ No newline at end of file
+}
diff --git a/karavan-designer/src/designer/route/element/DslElement.tsx b/karavan-designer/src/designer/route/element/DslElement.tsx
index 6e8ce718..a1b63a98 100644
--- a/karavan-designer/src/designer/route/element/DslElement.tsx
+++ b/karavan-designer/src/designer/route/element/DslElement.tsx
@@ -14,20 +14,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React, {CSSProperties, useMemo, useState} from 'react';
-import {Text, Tooltip,} from '@patternfly/react-core';
+import React, {CSSProperties, useState} from 'react';
+import {Tooltip,} from '@patternfly/react-core';
 import '../../karavan.css';
 import './DslElement.css';
 import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
-import {CamelUi} from "../../utils/CamelUi";
 import {EventBus} from "../../utils/EventBus";
 import {ChildElement, CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
-import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
 import {CamelDisplayUtil} from "karavan-core/lib/api/CamelDisplayUtil";
 import {useDesignerStore, useIntegrationStore} from "../../DesignerStore";
 import {shallow} from "zustand/shallow";
 import {useRouteDesignerHook} from "../useRouteDesignerHook";
-import {AddElementIcon, DeleteElementIcon, InsertElementIcon} from "./DslElementIcons";
+import {AddElementIcon} from "./DslElementIcons";
+import {DslElementHeader} from "./DslElementHeader";
 
 interface Props {
     step: CamelElement,
@@ -70,11 +69,6 @@ export function DslElement(props: Props) {
         }
     }
 
-    function onDeleteElement(evt: React.MouseEvent) {
-        evt.stopPropagation();
-        onShowDeleteConfirmation(props.step.uuid);
-    }
-
     function onSelectElement(evt: React.MouseEvent) {
         evt.stopPropagation();
         selectElement(props.step);
@@ -106,11 +100,11 @@ export function DslElement(props: Props) {
 
     function hasBorder(): boolean {
         const step = props.step;
-        if (['FilterDefinition'].includes(step.dslName)) {
+        if (['FilterDefinition', 'RouteDefinition', 'RouteConfigurationDefinition'].includes(step.dslName)) {
             return true;
         }
-        if (['FromDefinition',
-            'RouteDefinition',
+        if ([
+            'FromDefinition',
             'TryDefinition',
             'CatchDefinition', 'FinallyDefinition',
             'ChoiceDefinition',
@@ -125,9 +119,8 @@ export function DslElement(props: Props) {
         return ['FromDefinition', 'RouteConfigurationDefinition', 'RouteDefinition', 'WhenDefinition', 'OtherwiseDefinition'].includes(props.step.dslName);
     }
 
-    function isWide(): boolean {
-        return ['RouteConfigurationDefinition', 'RouteDefinition', 'ChoiceDefinition', 'SwitchDefinition', 'MulticastDefinition', 'TryDefinition', 'CircuitBreakerDefinition']
-            .includes(props.step.dslName);
+    function isRoute(): boolean {
+        return ['RouteDefinition'].includes(props.step.dslName);
     }
 
     function isAddStepButtonLeft(): boolean {
@@ -139,9 +132,6 @@ export function DslElement(props: Props) {
         return ['MulticastDefinition'].includes(props.step.dslName);
     }
 
-    function isRoot(): boolean {
-        return ['RouteConfigurationDefinition', 'RouteDefinition'].includes(props.step?.dslName);
-    }
 
     function isInStepWithChildren() {
         const step: CamelElement = props.step;
@@ -149,49 +139,6 @@ export function DslElement(props: Props) {
         return children.filter((c: ChildElement) => c.name === 'steps' || c.multiple).length > 0 && props.inSteps;
     }
 
-    function getChildrenInfo(step: CamelElement): [boolean, number, boolean, number, number] {
-        const children = CamelDefinitionApiExt.getElementChildrenDefinition(step.dslName);
-        const hasStepsField = children.filter((c: ChildElement) => c.name === 'steps').length === 1;
-        const stepsChildrenCount = children
-            .filter(c => c.name === 'steps')
-            .map((child: ChildElement, index: number) => {
-                const children: CamelElement[] = CamelDefinitionApiExt.getElementChildren(step, child);
-                return children.length;
-            }).reduce((a, b) => a + b, 0);
-
-        const hasNonStepsFields = children.filter(c => c.name !== 'steps' && c.name !== 'expression' && c.name !== 'onWhen').length > 0;
-        const childrenCount = children
-            .map((child: ChildElement, index: number) => {
-                const children: CamelElement[] = CamelDefinitionApiExt.getElementChildren(step, child);
-                return children.length;
-            }).reduce((a, b) => a + b, 0);
-        const nonStepChildrenCount = childrenCount - stepsChildrenCount;
-        return [hasStepsField, stepsChildrenCount, hasNonStepsFields, nonStepChildrenCount, childrenCount]
-    }
-
-    function hasWideChildrenElement() {
-        const [hasStepsField, stepsChildrenCount, hasNonStepsFields, nonStepChildrenCount, childrenCount] = getChildrenInfo(props.step);
-        if (isHorizontal() && stepsChildrenCount > 1) return true;
-        else if (hasStepsField && stepsChildrenCount > 0 && hasNonStepsFields && nonStepChildrenCount > 0) return true;
-        else if (!hasStepsField && hasNonStepsFields && childrenCount > 1) return true;
-        else if (hasStepsField && stepsChildrenCount > 0 && hasNonStepsFields && childrenCount > 1) return true;
-        else return false;
-    }
-
-    function hasBorderOverSteps(step: CamelElement) {
-        const [hasStepsField, stepsChildrenCount, hasNonStepsFields, nonStepChildrenCount] = getChildrenInfo(step);
-        if (hasStepsField && stepsChildrenCount > 0 && hasNonStepsFields && nonStepChildrenCount > 0) return true;
-        else return false;
-    }
-
-    function getHeaderStyle() {
-        const style: CSSProperties = {
-            width: isWide() ? "100%" : "",
-            fontWeight: isElementSelected() ? "bold" : "normal",
-        };
-        return style;
-    }
-
     function sendButtonPosition(el: HTMLButtonElement | null) {
         const {nextStep, step, parent} = props;
         let needArrow = !hasBorder() && !['ChoiceDefinition', 'MulticastDefinition', 'TryDefinition'].includes(step.dslName);
@@ -233,97 +180,6 @@ export function DslElement(props: Props) {
         }
     }
 
-    function getAvailableModels() { // TODO: make static list-of-values instead
-        const step: CamelElement = props.step
-        return CamelUi.getSelectorModelsForParent(step.dslName, false);
-    }
-
-    const availableModels = useMemo(
-        () => getAvailableModels(),
-        [props.step.dslName]
-    );
-
-
-    function getHeader() {
-        const step: CamelElement = props.step;
-        const parent = props.parent;
-        const inRouteConfiguration = parent !== undefined && parent.dslName === 'RouteConfigurationDefinition';
-        const showAddButton = !['CatchDefinition', 'RouteDefinition'].includes(step.dslName) && availableModels.length > 0;
-        const showInsertButton =
-            !['FromDefinition', 'RouteConfigurationDefinition', 'RouteDefinition', 'CatchDefinition', 'FinallyDefinition', 'WhenDefinition', 'OtherwiseDefinition'].includes(step.dslName)
-            && !inRouteConfiguration;
-        const headerClass = ['RouteConfigurationDefinition', 'RouteDefinition'].includes(step.dslName) ? "header-route" : "header"
-        const headerClasses = isElementSelected() ? headerClass + " selected" : headerClass;
-        return (
-            <div className={"dsl-element " + headerClasses} style={getHeaderStyle()} ref={headerRef}>
-                {!['RouteConfigurationDefinition', 'RouteDefinition'].includes(props.step.dslName) &&
-                    <div
-                        ref={el => sendPosition(el)}
-                        className={"header-icon"}
-                        style={isWide() ? {width: ""} : {}}>
-                        {CamelUi.getIconForElement(step)}
-                    </div>
-                }
-                <div className={hasWideChildrenElement() ? "header-text" : ""}>
-                    {hasWideChildrenElement() && <div className="spacer"/>}
-                    {getHeaderTextWithTooltip(step)}
-                </div>
-                {showInsertButton && getInsertElementButton()}
-                {getDeleteButton()}
-                {showAddButton && getAddElementButton()}
-            </div>
-        )
-    }
-
-    function getHeaderText(step: CamelElement): string {
-        if (isKamelet() && step.dslName === 'ToDefinition' && (step as any).uri === 'kamelet:sink') {
-            return "Sink";
-        } else if (isKamelet() && step.dslName === 'FromDefinition' && (step as any).uri === 'kamelet:source') {
-            return "Source";
-        } else {
-            return (step as any).description ? (step as any).description : CamelUi.getElementTitle(props.step);
-        }
-    }
-
-    function getHeaderTextWithTooltip(step: CamelElement) {
-        const checkRequired = CamelUtil.checkRequired(step);
-        const title = getHeaderText(step);
-        let className = hasWideChildrenElement() ? "text text-right" : "text text-bottom";
-        if (!checkRequired[0]) className = className + " header-text-required";
-        if (checkRequired[0]) {
-            return <Text className={className}>{title}</Text>
-        } else return (
-            <Tooltip position={"right"} className="tooltip-required-field"
-                     content={checkRequired[1].map((text, i) => (<div key={i}>{text}</div>))}>
-                <Text className={className}>{title}</Text>
-            </Tooltip>
-        )
-    }
-
-    function getHeaderWithTooltip(tooltip: string | undefined) {
-        return (
-            <>
-                {getHeader()}
-                <Tooltip triggerRef={headerRef} position={"left"} content={<div>{tooltip}</div>}/>
-            </>
-
-        )
-    }
-
-    function getHeaderTooltip(): string | undefined {
-        if (CamelUi.isShowExpressionTooltip(props.step)) return CamelUi.getExpressionTooltip(props.step);
-        if (CamelUi.isShowUriTooltip(props.step)) return CamelUi.getUriTooltip(props.step);
-        return undefined;
-    }
-
-    function getElementHeader() {
-        const tooltip = getHeaderTooltip();
-        if (tooltip !== undefined && !isDragging) {
-            return getHeaderWithTooltip(tooltip);
-        }
-        return getHeader();
-    }
-
     function getChildrenStyle() {
         const style: CSSProperties = {
             display: "flex",
@@ -333,8 +189,6 @@ export function DslElement(props: Props) {
     }
 
     function getChildrenElementsStyle(child: ChildElement, notOnlySteps: boolean) {
-        const step = props.step;
-        const isBorder = child.name === 'steps' && hasBorderOverSteps(step);
         const style: CSSProperties = {
             // borderStyle: isBorder ? "dotted" : "none",
             borderColor: "var(--step-border-color)",
@@ -408,7 +262,7 @@ export function DslElement(props: Props) {
     }
 
     function getAddStepButton() {
-        const {step, nextStep} = props;
+        const {step} = props;
         const hideAddButton = step.dslName === 'StepDefinition' && !CamelDisplayUtil.isStepDefinitionExpanded(integration, step.uuid, selectedUuids.at(0));
         if (hideAddButton) return (<></>)
         else return (
@@ -429,44 +283,6 @@ export function DslElement(props: Props) {
         )
     }
 
-    function getAddElementButton() {
-        return (
-            <Tooltip position={"bottom"}
-                     content={<div>{"Add DSL element to " + CamelDisplayUtil.getTitle(props.step)}</div>}>
-                <button
-                    type="button"
-                    aria-label="Add"
-                    onClick={e => onOpenSelector(e, false)}
-                    className={"add-element-button"}>
-                    <AddElementIcon/>
-                </button>
-            </Tooltip>
-        )
-    }
-
-    function getInsertElementButton() {
-        return (
-            <Tooltip position={"left"} content={<div>{"Insert element before"}</div>}>
-                <button type="button"
-                        aria-label="Insert"
-                        onClick={e => onOpenSelector(e, true, true)}
-                        className={"insert-element-button"}>
-                    <InsertElementIcon/>
-                </button>
-            </Tooltip>
-        )
-    }
-
-    function getDeleteButton() {
-        return (
-            <Tooltip position={"right"} content={<div>{"Delete element"}</div>}>
-                <button type="button" aria-label="Delete" onClick={e => onDeleteElement(e)} className="delete-button">
-                    <DeleteElementIcon/>
-                </button>
-            </Tooltip>
-        )
-    }
-
     const element: CamelElement = props.step;
     const className = "step-element"
         + (isElementSelected() ? " step-element-selected" : "") + (!props.step.showChildren ? " hidden-step" : "")
@@ -516,7 +332,14 @@ export function DslElement(props: Props) {
              onDrop={event => dragElement(event, element)}
              draggable={!isNotDraggable()}
         >
-            {getElementHeader()}
+            <DslElementHeader headerRef={headerRef}
+                              step={props.step}
+                              parent={props.parent}
+                              nextStep={props.nextStep}
+                              prevStep={props.prevStep}
+                              inSteps={props.inSteps}
+                              isDragging={isDragging}
+                              position={props.position}/>
             {getChildElements()}
         </div>
     )
diff --git a/karavan-designer/src/designer/route/element/DslElement.tsx b/karavan-designer/src/designer/route/element/DslElementHeader.tsx
similarity index 50%
copy from karavan-designer/src/designer/route/element/DslElement.tsx
copy to karavan-designer/src/designer/route/element/DslElementHeader.tsx
index 6e8ce718..9ac4ab99 100644
--- a/karavan-designer/src/designer/route/element/DslElement.tsx
+++ b/karavan-designer/src/designer/route/element/DslElementHeader.tsx
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React, {CSSProperties, useMemo, useState} from 'react';
+import React, {CSSProperties, useMemo} from 'react';
 import {Text, Tooltip,} from '@patternfly/react-core';
 import '../../karavan.css';
 import './DslElement.css';
@@ -24,24 +24,29 @@ import {EventBus} from "../../utils/EventBus";
 import {ChildElement, CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
 import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
 import {CamelDisplayUtil} from "karavan-core/lib/api/CamelDisplayUtil";
-import {useDesignerStore, useIntegrationStore} from "../../DesignerStore";
+import {useDesignerStore} from "../../DesignerStore";
 import {shallow} from "zustand/shallow";
 import {useRouteDesignerHook} from "../useRouteDesignerHook";
 import {AddElementIcon, DeleteElementIcon, InsertElementIcon} from "./DslElementIcons";
+import {
+    InterceptDefinition,
+    InterceptFromDefinition,
+    InterceptSendToEndpointDefinition, OnCompletionDefinition, OnExceptionDefinition, RouteConfigurationDefinition
+} from "karavan-core/lib/model/CamelDefinition";
 
 interface Props {
+    headerRef: React.RefObject<HTMLDivElement>
     step: CamelElement,
     parent: CamelElement | undefined,
     nextStep: CamelElement | undefined,
     prevStep: CamelElement | undefined,
     inSteps: boolean
     position: number
+    isDragging: boolean
 }
 
-export function DslElement(props: Props) {
+export function DslElementHeader(props: Props) {
 
-    const headerRef = React.useRef<HTMLDivElement>(null);
-    const addButtonRef = React.useRef<HTMLDivElement>(null);
     const {
         selectElement,
         moveElement,
@@ -52,14 +57,9 @@ export function DslElement(props: Props) {
         isActionKamelet
     } = useRouteDesignerHook();
 
-    const [integration] = useIntegrationStore((s) => [s.integration, s.setIntegration], shallow)
-
     const [selectedUuids, selectedStep, showMoveConfirmation, setShowMoveConfirmation, hideLogDSL, setMoveElements] =
         useDesignerStore((s) =>
             [s.selectedUuids, s.selectedStep, s.showMoveConfirmation, s.setShowMoveConfirmation, s.hideLogDSL, s.setMoveElements], shallow)
-    const [isDragging, setIsDragging] = useState<boolean>(false);
-
-    const [isDraggedOver, setIsDraggedOver] = useState<boolean>(false);
 
     function onOpenSelector(evt: React.MouseEvent, showSteps: boolean = true, isInsert: boolean = false) {
         evt.stopPropagation();
@@ -75,27 +75,6 @@ export function DslElement(props: Props) {
         onShowDeleteConfirmation(props.step.uuid);
     }
 
-    function onSelectElement(evt: React.MouseEvent) {
-        evt.stopPropagation();
-        selectElement(props.step);
-    }
-
-    function dragElement(event: React.DragEvent<HTMLDivElement>, element: CamelElement) {
-        event.preventDefault();
-        event.stopPropagation();
-        setIsDraggedOver(false);
-        const sourceUuid = event.dataTransfer.getData("text/plain");
-        const targetUuid = element.uuid;
-        if (sourceUuid !== targetUuid) {
-            if (element.hasSteps()) {
-                setShowMoveConfirmation(true);
-                setMoveElements([sourceUuid, targetUuid])
-            } else {
-                moveElement(sourceUuid, targetUuid, false);
-            }
-        }
-    }
-
     function isElementSelected(): boolean {
         return selectedUuids.includes(props.step.uuid);
     }
@@ -104,51 +83,15 @@ export function DslElement(props: Props) {
         return props.step.dslName === 'LogDefinition' && hideLogDSL;
     }
 
-    function hasBorder(): boolean {
-        const step = props.step;
-        if (['FilterDefinition'].includes(step.dslName)) {
-            return true;
-        }
-        if (['FromDefinition',
-            'RouteDefinition',
-            'TryDefinition',
-            'CatchDefinition', 'FinallyDefinition',
-            'ChoiceDefinition',
-            'SwitchDefinition', 'WhenDefinition', 'OtherwiseDefinition'
-        ].includes(step.dslName)) {
-            return false;
-        }
-        return props.step?.hasSteps();
-    }
-
-    function isNotDraggable(): boolean {
-        return ['FromDefinition', 'RouteConfigurationDefinition', 'RouteDefinition', 'WhenDefinition', 'OtherwiseDefinition'].includes(props.step.dslName);
-    }
-
     function isWide(): boolean {
         return ['RouteConfigurationDefinition', 'RouteDefinition', 'ChoiceDefinition', 'SwitchDefinition', 'MulticastDefinition', 'TryDefinition', 'CircuitBreakerDefinition']
             .includes(props.step.dslName);
     }
 
-    function isAddStepButtonLeft(): boolean {
-        return ['MulticastDefinition']
-            .includes(props.step.dslName);
-    }
-
     function isHorizontal(): boolean {
         return ['MulticastDefinition'].includes(props.step.dslName);
     }
 
-    function isRoot(): boolean {
-        return ['RouteConfigurationDefinition', 'RouteDefinition'].includes(props.step?.dslName);
-    }
-
-    function isInStepWithChildren() {
-        const step: CamelElement = props.step;
-        const children = CamelDefinitionApiExt.getElementChildrenDefinition(step.dslName);
-        return children.filter((c: ChildElement) => c.name === 'steps' || c.multiple).length > 0 && props.inSteps;
-    }
-
     function getChildrenInfo(step: CamelElement): [boolean, number, boolean, number, number] {
         const children = CamelDefinitionApiExt.getElementChildrenDefinition(step.dslName);
         const hasStepsField = children.filter((c: ChildElement) => c.name === 'steps').length === 1;
@@ -178,12 +121,6 @@ export function DslElement(props: Props) {
         else return false;
     }
 
-    function hasBorderOverSteps(step: CamelElement) {
-        const [hasStepsField, stepsChildrenCount, hasNonStepsFields, nonStepChildrenCount] = getChildrenInfo(step);
-        if (hasStepsField && stepsChildrenCount > 0 && hasNonStepsFields && nonStepChildrenCount > 0) return true;
-        else return false;
-    }
-
     function getHeaderStyle() {
         const style: CSSProperties = {
             width: isWide() ? "100%" : "",
@@ -192,24 +129,6 @@ export function DslElement(props: Props) {
         return style;
     }
 
-    function sendButtonPosition(el: HTMLButtonElement | null) {
-        const {nextStep, step, parent} = props;
-        let needArrow = !hasBorder() && !['ChoiceDefinition', 'MulticastDefinition', 'TryDefinition'].includes(step.dslName);
-
-        if (parent
-            && ['TryDefinition'].includes(parent.dslName)
-            && !['CatchDefinition', 'FinallyDefinition'].includes(step.dslName)) {
-            needArrow = true;
-        }
-
-        if (el && nextStep && needArrow) {
-            const rect = headerRef.current?.getBoundingClientRect();
-
-            if (rect)
-                EventBus.sendButtonPosition("add", step.uuid, nextStep, rect);
-        }
-    }
-
     function sendPosition(el: HTMLDivElement | null) {
         const {step, prevStep, parent} = props;
         const isSelected = isElementSelected();
@@ -243,6 +162,30 @@ export function DslElement(props: Props) {
         [props.step.dslName]
     );
 
+    function hasElements(rc: RouteConfigurationDefinition): boolean {
+        return (rc.interceptFrom !== undefined && rc.interceptFrom.length > 0)
+    || (rc.intercept !== undefined && rc.intercept.length > 0)
+    || (rc.interceptSendToEndpoint !== undefined && rc.interceptSendToEndpoint.length > 0)
+    || (rc.onException !== undefined && rc.onException.length > 0)
+    || (rc.onCompletion !== undefined && rc.onCompletion.length > 0)
+    }
+
+    function getHeaderClasses(): string {
+        const classes: string[] = [];
+        const step: CamelElement = props.step;
+        if (step.dslName === 'RouteDefinition') {
+            classes.push(...'header-route', 'header-bottom-line')
+        } else if (step.dslName === 'RouteConfigurationDefinition') {
+            classes.push('header-route')
+            if (hasElements(step)) classes.push('header-bottom-line')
+        } else {
+            classes.push('header')
+        }
+        if (isElementSelected()) {
+            classes.push("selected")
+        }
+        return classes.join(" ");
+    }
 
     function getHeader() {
         const step: CamelElement = props.step;
@@ -252,10 +195,9 @@ export function DslElement(props: Props) {
         const showInsertButton =
             !['FromDefinition', 'RouteConfigurationDefinition', 'RouteDefinition', 'CatchDefinition', 'FinallyDefinition', 'WhenDefinition', 'OtherwiseDefinition'].includes(step.dslName)
             && !inRouteConfiguration;
-        const headerClass = ['RouteConfigurationDefinition', 'RouteDefinition'].includes(step.dslName) ? "header-route" : "header"
-        const headerClasses = isElementSelected() ? headerClass + " selected" : headerClass;
+        const headerClasses = getHeaderClasses();
         return (
-            <div className={"dsl-element " + headerClasses} style={getHeaderStyle()} ref={headerRef}>
+            <div className={"dsl-element " + headerClasses} style={getHeaderStyle()} ref={props.headerRef}>
                 {!['RouteConfigurationDefinition', 'RouteDefinition'].includes(props.step.dslName) &&
                     <div
                         ref={el => sendPosition(el)}
@@ -286,8 +228,8 @@ export function DslElement(props: Props) {
     }
 
     function getHeaderTextWithTooltip(step: CamelElement) {
-        const checkRequired = CamelUtil.checkRequired(step);
         const title = getHeaderText(step);
+        const checkRequired = CamelUtil.checkRequired(step);
         let className = hasWideChildrenElement() ? "text text-right" : "text text-bottom";
         if (!checkRequired[0]) className = className + " header-text-required";
         if (checkRequired[0]) {
@@ -304,7 +246,7 @@ export function DslElement(props: Props) {
         return (
             <>
                 {getHeader()}
-                <Tooltip triggerRef={headerRef} position={"left"} content={<div>{tooltip}</div>}/>
+                <Tooltip triggerRef={props.headerRef} position={"left"} content={<div>{tooltip}</div>}/>
             </>
 
         )
@@ -316,118 +258,6 @@ export function DslElement(props: Props) {
         return undefined;
     }
 
-    function getElementHeader() {
-        const tooltip = getHeaderTooltip();
-        if (tooltip !== undefined && !isDragging) {
-            return getHeaderWithTooltip(tooltip);
-        }
-        return getHeader();
-    }
-
-    function getChildrenStyle() {
-        const style: CSSProperties = {
-            display: "flex",
-            flexDirection: "row",
-        }
-        return style;
-    }
-
-    function getChildrenElementsStyle(child: ChildElement, notOnlySteps: boolean) {
-        const step = props.step;
-        const isBorder = child.name === 'steps' && hasBorderOverSteps(step);
-        const style: CSSProperties = {
-            // borderStyle: isBorder ? "dotted" : "none",
-            borderColor: "var(--step-border-color)",
-            borderWidth: "1px",
-            borderRadius: "16px",
-            display: isHorizontal() || child.name !== 'steps' ? "flex" : "block",
-            flexDirection: "row",
-        }
-        return style;
-    }
-
-    function getChildElements() {
-        const step: CamelElement = props.step;
-        let children: ChildElement[] = CamelDefinitionApiExt.getElementChildrenDefinition(step.dslName);
-        const notOnlySteps = children.filter(c => c.name === 'steps').length === 1
-            && children.filter(c => c.multiple && c.name !== 'steps').length > 0;
-
-        if (step.dslName !== 'RouteDefinition') {
-            children = children.filter(child => {
-                const cc = CamelDefinitionApiExt.getElementChildrenDefinition(child.className);
-                return child.name === 'steps' || cc.filter(c => c.multiple).length > 0;
-            })
-        }
-        if (step.dslName === 'CatchDefinition') { // exception
-            children = children.filter(value => value.name !== 'onWhen')
-        }
-        return (
-            <div key={step.uuid + "-children"} className="children" style={getChildrenStyle()}>
-                {children.map((child: ChildElement, index: number) => getChildDslElements(child, index, notOnlySteps))}
-            </div>
-        )
-    }
-
-    function getChildDslElements(child: ChildElement, index: number, notOnlySteps: boolean) {
-        const step = props.step;
-        const children: CamelElement[] = CamelDefinitionApiExt.getElementChildren(step, child);
-        if (children.length > 0) {
-            return (
-                <div className={child.name + " has-child"} style={getChildrenElementsStyle(child, notOnlySteps)}
-                     key={step.uuid + "-child-" + index}>
-                    {children.map((element, index) => {
-                            let prevStep = children.at(index - 1);
-                            let nextStep: CamelElement | undefined = undefined;
-                            if (['TryDefinition', 'ChoiceDefinition'].includes(step.dslName)) {
-                                nextStep = props.nextStep;
-                            } else {
-                                nextStep = children.at(index + 1);
-                            }
-                            return (<div key={step.uuid + child.className + index}>
-                                <DslElement
-                                    inSteps={child.name === 'steps'}
-                                    position={index}
-                                    step={element}
-                                    nextStep={nextStep}
-                                    prevStep={prevStep}
-                                    parent={step}/>
-                            </div>)
-                        }
-                    )}
-                    {child.name === 'steps' && getAddStepButton()}
-                </div>
-            )
-        } else if (child.name === 'steps') {
-            return (
-                <div className={child.name + " has-child"} style={getChildrenElementsStyle(child, notOnlySteps)}
-                     key={step.uuid + "-child-" + index}>
-                    {getAddStepButton()}
-                </div>
-            )
-        }
-    }
-
-    function getAddStepButton() {
-        const {step, nextStep} = props;
-        const hideAddButton = step.dslName === 'StepDefinition' && !CamelDisplayUtil.isStepDefinitionExpanded(integration, step.uuid, selectedUuids.at(0));
-        if (hideAddButton) return (<></>)
-        else return (
-            <div ref={addButtonRef}>
-                <Tooltip position={"bottom"}
-                         content={<div>{"Add step to " + CamelDisplayUtil.getTitle(step)}</div>}
-                >
-                    <button type="button"
-                            ref={el => sendButtonPosition(el)}
-                            aria-label="Add"
-                            onClick={e => onOpenSelector(e)}
-                            className={isAddStepButtonLeft() ? "add-button add-button-left" : "add-button add-button-bottom"}>
-                        <AddElementIcon/>
-                    </button>
-
-                </Tooltip>
-            </div>
-        )
-    }
 
     function getAddElementButton() {
         return (
@@ -467,57 +297,9 @@ export function DslElement(props: Props) {
         )
     }
 
-    const element: CamelElement = props.step;
-    const className = "step-element"
-        + (isElementSelected() ? " step-element-selected" : "") + (!props.step.showChildren ? " hidden-step" : "")
-        + ((element as any).disabled ? " disabled " : "");
-    return (
-        <div key={"root" + element.uuid}
-             className={className}
-             ref={el => sendPosition(el)}
-             style={{
-                 borderStyle: hasBorder() ? "dashed" : "none",
-                 borderColor: isElementSelected() ? "var(--step-border-color-selected)" : "var(--step-border-color)",
-                 marginTop: isInStepWithChildren() ? "16px" : "8px",
-                 zIndex: element.dslName === 'ToDefinition' ? 20 : 10,
-                 boxShadow: isDraggedOver ? "0px 0px 1px 2px var(--step-border-color-selected)" : "none",
-             }}
-             onMouseOver={event => event.stopPropagation()}
-             onClick={event => onSelectElement(event)}
-             onDragStart={event => {
-                 event.stopPropagation();
-                 event.dataTransfer.setData("text/plain", element.uuid);
-                 (event.target as any).style.opacity = .5;
-                 setIsDragging(true);
-             }}
-             onDragEnd={event => {
-                 (event.target as any).style.opacity = '';
-                 setIsDragging(false);
-             }}
-             onDragOver={event => {
-                 event.preventDefault();
-                 event.stopPropagation();
-                 if (element.dslName !== 'FromDefinition' && !isDragging) {
-                     setIsDraggedOver(true);
-                 }
-             }}
-             onDragEnter={event => {
-                 event.preventDefault();
-                 event.stopPropagation();
-                 if (element.dslName !== 'FromDefinition') {
-                     setIsDraggedOver(true);
-                 }
-             }}
-             onDragLeave={event => {
-                 event.preventDefault();
-                 event.stopPropagation();
-                 setIsDraggedOver(false);
-             }}
-             onDrop={event => dragElement(event, element)}
-             draggable={!isNotDraggable()}
-        >
-            {getElementHeader()}
-            {getChildElements()}
-        </div>
-    )
+    const tooltip = getHeaderTooltip();
+    if (tooltip !== undefined && !props.isDragging) {
+        return getHeaderWithTooltip(tooltip);
+    }
+    return getHeader();
 }
diff --git a/karavan-designer/src/designer/utils/CamelUi.tsx b/karavan-designer/src/designer/utils/CamelUi.tsx
index 61a7d27c..5caae0a7 100644
--- a/karavan-designer/src/designer/utils/CamelUi.tsx
+++ b/karavan-designer/src/designer/utils/CamelUi.tsx
@@ -337,7 +337,7 @@ export class CamelUi {
     static getElementTitle = (element: CamelElement): string => {
         if (element.dslName === 'RouteDefinition') {
             const routeId = (element as RouteDefinition).id
-            return routeId ? "Route: " + routeId : CamelUtil.capitalizeName((element as any).stepName);
+            return routeId ? routeId : CamelUtil.capitalizeName((element as any).stepName);
         } else if (['ToDefinition', 'ToDynamicDefinition', 'FromDefinition', 'KameletDefinition'].includes(element.dslName) && (element as any).uri) {
             const uri = (element as any).uri;
             const kameletTitle = uri && uri.startsWith("kamelet:") ? KameletApi.findKameletByUri(uri)?.title() : undefined;
@@ -787,4 +787,5 @@ export class CamelUi {
             .forEach((f: any) => result.push(f));
         return result;
     }
+
 }
\ No newline at end of file


(camel-karavan) 03/04: Preview fixes of new Designer

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 5eb8615b4af9e00fd2b6ed674b74073060d97546
Author: Marat Gubaidullin <ma...@talismancloud.io>
AuthorDate: Wed Dec 6 21:33:23 2023 -0500

    Preview fixes of new Designer
---
 karavan-designer/src/designer/DesignerStore.ts     | 31 ++--------
 .../src/designer/route/DslConnections.tsx          | 72 ++++++++++------------
 .../src/designer/route/element/DslElement.tsx      |  5 +-
 .../src/designer/route/useRouteDesignerHook.tsx    |  1 -
 karavan-designer/src/designer/utils/EventBus.ts    | 23 -------
 5 files changed, 40 insertions(+), 92 deletions(-)

diff --git a/karavan-designer/src/designer/DesignerStore.ts b/karavan-designer/src/designer/DesignerStore.ts
index 815f0f68..407713a7 100644
--- a/karavan-designer/src/designer/DesignerStore.ts
+++ b/karavan-designer/src/designer/DesignerStore.ts
@@ -16,7 +16,7 @@
  */
 
 import {CamelElement, Integration} from "karavan-core/lib/model/IntegrationDefinition";
-import {ButtonPosition, DslPosition, EventBus} from "./utils/EventBus";
+import {DslPosition, EventBus} from "./utils/EventBus";
 import {createWithEqualityFn} from "zustand/traditional";
 import {shallow} from "zustand/shallow";
 
@@ -120,10 +120,6 @@ interface ConnectionsState {
     deleteStep: (uuid: string) => void;
     clearSteps: () => void;
     setSteps: (steps: Map<string, DslPosition>) => void;
-    buttons: Map<string, ButtonPosition>;
-    addButton: (uuid: string, button: ButtonPosition) => void;
-    deleteButton: (uuid: string) => void;
-    clearButtons: () => void;
 }
 
 export const useConnectionsStore = createWithEqualityFn<ConnectionsState>((set) => ({
@@ -138,6 +134,8 @@ export const useConnectionsStore = createWithEqualityFn<ConnectionsState>((set)
             // state.steps.clear();
             Array.from(state.steps.entries())
                 .filter(value => value[1]?.parent?.uuid !== uuid)
+                .filter(value => value[1]?.prevStep?.uuid !== uuid)
+                .filter(value => value[1]?.nextstep?.uuid !== uuid)
                 .forEach(value => state.steps.set(value[0], value[1]));
             state.steps.delete(uuid)
             return state;
@@ -151,28 +149,7 @@ export const useConnectionsStore = createWithEqualityFn<ConnectionsState>((set)
     },
     setSteps: (steps: Map<string, DslPosition>) => {
         set({steps: steps})
-    },
-    buttons: new Map<string, ButtonPosition>(),
-    addButton: (uuid: string, button: ButtonPosition) => {
-        set(state => ({
-            buttons: new Map(state.buttons).set(uuid, button),
-        }))
-    },
-    clearButtons: () => {
-        set((state: ConnectionsState) => {
-            state.buttons.clear();
-            return state;
-        })
-    },
-    deleteButton: (uuid: string) => {
-        set((state: ConnectionsState) => {
-            Array.from(state.buttons.entries())
-                .filter(value => value[1].uuid !== uuid)
-                .forEach(value => state.buttons.set(value[0], value[1]));
-            state.buttons.delete(uuid)
-            return state;
-        })
-    },
+    }
 }), shallow)
 
 type DesignerState = {
diff --git a/karavan-designer/src/designer/route/DslConnections.tsx b/karavan-designer/src/designer/route/DslConnections.tsx
index 934fd89c..c7651aac 100644
--- a/karavan-designer/src/designer/route/DslConnections.tsx
+++ b/karavan-designer/src/designer/route/DslConnections.tsx
@@ -14,15 +14,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React, {JSX, useEffect} from 'react';
+import React, {JSX, useEffect, useState} from 'react';
 import '../karavan.css';
-import {ButtonPosition, DslPosition, EventBus} from "../utils/EventBus";
+import {DslPosition, EventBus} from "../utils/EventBus";
 import {CamelUi} from "../utils/CamelUi";
 import {useConnectionsStore, useDesignerStore, useIntegrationStore} from "../DesignerStore";
 import {shallow} from "zustand/shallow";
 import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
 import {TopologyUtils} from "karavan-core/lib/api/TopologyUtils";
-import {CamelElement} from "../../../../karavan-core/lib/model/IntegrationDefinition";
+import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
+import {v4 as uuidv4} from "uuid";
 
 const overlapGap: number = 40;
 
@@ -31,36 +32,24 @@ export function DslConnections() {
     const [integration] = useIntegrationStore((state) => [state.integration], shallow)
     const [width, height, top, left, hideLogDSL] = useDesignerStore((s) =>
         [s.width, s.height, s.top, s.left, s.hideLogDSL], shallow)
-    const [steps, addStep, deleteStep, clearSteps, buttons, addButton, clearButtons, deleteButton] =
-        useConnectionsStore((s) => [s.steps, s.addStep, s.deleteStep, s.clearSteps,
-            s.buttons, s.addButton, s.clearButtons, s.deleteButton], shallow)
+    const [steps, addStep, deleteStep, clearSteps] =
+        useConnectionsStore((s) => [s.steps, s.addStep, s.deleteStep, s.clearSteps], shallow)
+
+    const [svgKey, setSvgKey] = useState<string>('svgKey');
 
     useEffect(() => {
         const sub1 = EventBus.onPosition()?.subscribe((evt: DslPosition) => setPosition(evt));
-        const sub2 = EventBus.onButtonPosition()?.subscribe((btn: ButtonPosition) => setButtonPosition(btn));
         return () => {
             sub1?.unsubscribe();
-            sub2?.unsubscribe();
         };
     });
 
     useEffect(() => {
         const toDelete1: string[] = Array.from(steps.keys()).filter(k => CamelDefinitionApiExt.findElementInIntegration(integration, k) === undefined);
         toDelete1.forEach(key => deleteStep(key));
-        const toDelete2: string[] = Array.from(buttons.keys()).filter(k => CamelDefinitionApiExt.findElementInIntegration(integration, k) === undefined);
-        toDelete2.forEach(key => deleteButton(key));
+        setSvgKey(uuidv4())
     }, [integration]);
 
-    function setButtonPosition(btn: ButtonPosition) {
-        if (btn.command === "add") {
-            addButton(btn.uuid, btn);
-        } else if (btn.command === "delete") {
-            deleteButton(btn.uuid);
-        } else if (btn.command === "clean") {
-            clearButtons();
-        }
-    }
-
     function setPosition(evt: DslPosition) {
         if (evt.command === "add") {
             addStep(evt.step.uuid, evt);
@@ -242,7 +231,8 @@ export function DslConnections() {
         if (from && to) {
             const rect1 = fromHeader === true ? from.headerRect : from.rect;
             const rect2 = toHeader === true ? to.headerRect : to.rect;
-            result.push(getComplexArrow(from.step.uuid + "->" + to.step.uuid, rect1, rect2, toHeader === true));
+            const key = from.step.uuid + "->" + to.step.uuid;
+            result.push(getComplexArrow(key, rect1, rect2, toHeader === true));
         }
         return result;
     }
@@ -250,15 +240,9 @@ export function DslConnections() {
     function getArrow(pos: DslPosition): JSX.Element[] {
         const list: JSX.Element[] = [];
 
-        if (pos.parent && pos.parent.dslName === 'FromDefinition' && pos.position === 0) {
-            // const parent = steps.get(pos.parent.uuid);
-            // list.push(...addArrowToList(list, parent, pos, true, false))
-        } else if (pos.parent && pos.parent.dslName === 'TryDefinition' && pos.position === 0) {
+         if (pos.parent && pos.parent.dslName === 'TryDefinition' && pos.position === 0) {
             const parent = steps.get(pos.parent.uuid);
             list.push(...addArrowToList(list, parent, pos, true, false))
-        } else if (pos.parent && ['CatchDefinition', 'FinallyDefinition'].includes(pos.parent.dslName)  && pos.position === 0) {
-            const parent = steps.get(pos.parent.uuid);
-            list.push(...addArrowToList(list, parent, pos, true, true))
         } else if (pos.parent && pos.parent.dslName === 'MulticastDefinition') {
             const parent = steps.get(pos.parent.uuid);
             list.push(...addArrowToList(list, parent, pos, true, false))
@@ -315,10 +299,23 @@ export function DslConnections() {
             }
         }
 
-        if (!isSpecial(pos) && pos.inSteps && pos.nextstep && !pos.step.hasSteps() && pos.parent?.dslName !== 'MulticastDefinition') {
-            const to = steps.get(pos.nextstep.uuid);
-            list.push(...addArrowToList(list, pos, to, true, true))
+        if (!isSpecial(pos) && pos.inSteps && pos.nextstep && pos.parent?.dslName !== 'MulticastDefinition') {
+            const next = steps.get(pos.nextstep.uuid);
+            if (pos.step.hasSteps() && pos.prevStep) {
+            } else {
+                list.push(...addArrowToList(list, pos, next, true, true))
+            }
+        }
+
+        if (!isSpecial(pos) && pos.inSteps && pos.nextstep && pos.parent?.dslName !== 'MulticastDefinition') {
+            const next = steps.get(pos.nextstep.uuid);
+            if (next && !isSpecial(next) && next.inSteps) {
+                // console.log(pos)
+                // const to = steps.get(parent.nextstep.uuid);
+                // list.push(...addArrowToList(list, pos, to, true, true))
+            }
         }
+
         return list;
     }
 
@@ -360,26 +357,25 @@ export function DslConnections() {
                 + ` L ${LX2} ${LY2}`
                 + ` Q ${Q2_X1} ${Q2_Y1} ${Q2_X2} ${Q2_Y2}`
             return (
-                <path key={key} name={key} d={path} className="path" markerEnd="url(#arrowhead)"/>
+                <path key={uuidv4()} name={key} d={path} className="path" markerEnd="url(#arrowhead)"/>
             )
     }
 
     function getSvg() {
         const stepsArray = Array.from(steps.values());
+        const arrows = stepsArray.map(pos => getArrow(pos)).flat(1);
+        const uniqueArrows = [...new Map(arrows.map(item =>  [(item as any).key, item])).values()]
         return (
-            <svg
+            <svg key={svgKey}
                 style={{width: width, height: height, position: "absolute", left: 0, top: 0}}
                 viewBox={"0 0 " + (width) + " " + (height)}>
                 <defs>
-                    <marker id="arrowhead" markerWidth="9" markerHeight="6" refX="0" refY="3" orient="auto"
-                            className="arrow">
+                    <marker id="arrowhead" markerWidth="9" markerHeight="6" refX="0" refY="3" orient="auto" className="arrow">
                         <polygon points="0 0, 9 3, 0 6"/>
                     </marker>
                 </defs>
                 {stepsArray.map(pos => getCircle(pos))}
-                <g>
-                    {stepsArray.map(pos => getArrow(pos)).flat(1)}
-                </g>
+                {uniqueArrows}
                 {getIncomings().map(p => getIncoming(p))}
                 {getOutgoings().map(p => getOutgoing(p))}
             </svg>
diff --git a/karavan-designer/src/designer/route/element/DslElement.tsx b/karavan-designer/src/designer/route/element/DslElement.tsx
index 8f89a0f9..967d3321 100644
--- a/karavan-designer/src/designer/route/element/DslElement.tsx
+++ b/karavan-designer/src/designer/route/element/DslElement.tsx
@@ -27,7 +27,6 @@ import {shallow} from "zustand/shallow";
 import {useRouteDesignerHook} from "../useRouteDesignerHook";
 import {AddElementIcon} from "./DslElementIcons";
 import {DslElementHeader} from "./DslElementHeader";
-import {TryDefinition} from "karavan-core/lib/model/CamelDefinition";
 
 interface Props {
     step: CamelElement,
@@ -145,10 +144,10 @@ export function DslElement(props: Props) {
                 const rect = el.getBoundingClientRect();
                 if (step.showChildren) {
                     EventBus.sendPosition("add", step, prevStep, nextStep, parent, rect, headerRect, props.position, inStepsLength, inSteps, isSelected);
-                } else {
-                    EventBus.sendPosition("delete", step, prevStep, nextStep, parent, new DOMRect(), new DOMRect(), 0, 0);
                 }
             }
+        } else {
+            EventBus.sendPosition("delete", step, prevStep, nextStep, parent, new DOMRect(), new DOMRect(), 0, 0);
         }
     }
 
diff --git a/karavan-designer/src/designer/route/useRouteDesignerHook.tsx b/karavan-designer/src/designer/route/useRouteDesignerHook.tsx
index 0f90bb10..1457adba 100644
--- a/karavan-designer/src/designer/route/useRouteDesignerHook.tsx
+++ b/karavan-designer/src/designer/route/useRouteDesignerHook.tsx
@@ -106,7 +106,6 @@ export function useRouteDesignerHook () {
 
     const deleteElement = () =>  {
         EventBus.sendPosition("clean", new CamelElement(""), undefined,undefined, undefined, new DOMRect(), new DOMRect(), 0, 0);
-        EventBus.sendButtonPosition("clean", '',  new CamelElement(""), new DOMRect());
         let i = integration;
         selectedUuids.forEach(uuidToDelete => {
              i = CamelDefinitionApiExt.deleteStepFromIntegration(i, uuidToDelete);
diff --git a/karavan-designer/src/designer/utils/EventBus.ts b/karavan-designer/src/designer/utils/EventBus.ts
index 2d075b00..687bec9d 100644
--- a/karavan-designer/src/designer/utils/EventBus.ts
+++ b/karavan-designer/src/designer/utils/EventBus.ts
@@ -18,23 +18,6 @@ import {Subject} from 'rxjs';
 import {CamelElement, Integration} from "karavan-core/lib/model/IntegrationDefinition";
 import {v4 as uuidv4} from "uuid";
 
-export class ButtonPosition {
-    uuid: string = '';
-    nextstep: CamelElement = new CamelElement("");
-    rect: DOMRect = new DOMRect();
-    command: "add" | "delete" | "clean" = "add";
-
-    constructor(command: "add" | "delete" | "clean",
-                uuid: string,
-                nextstep: CamelElement,
-                rect: DOMRect) {
-        this.uuid = uuid;
-        this.command = command;
-        this.nextstep = nextstep;
-        this.rect = rect;
-    }
-}
-
 export class DslPosition {
     step: CamelElement = new CamelElement("");
     prevStep: CamelElement | undefined;
@@ -110,7 +93,6 @@ export class ToastMessage {
     }
 }
 const dslPositions = new Subject<DslPosition>();
-const buttonPositions = new Subject<ButtonPosition>();
 
 export const EventBus = {
     sendPosition: (command: "add" | "delete" | "clean",
@@ -127,11 +109,6 @@ export const EventBus = {
                        new DslPosition(command, step, prevStep, nextstep, parent, rect, headerRect, position, inStepsLength, inSteps, isSelected)),
     onPosition: () => dslPositions.asObservable(),
 
-    sendButtonPosition: (command: "add" | "delete" | "clean", uuid: string,
-                   nextStep: CamelElement,
-                   rect: DOMRect) => buttonPositions.next(new ButtonPosition(command, uuid, nextStep, rect)),
-    onButtonPosition: () => buttonPositions.asObservable(),
-
     sendIntegrationUpdate: (i: Integration, propertyOnly: boolean) => updates.next(new IntegrationUpdate(i, propertyOnly)),
     onIntegrationUpdate: () => updates.asObservable(),
 


(camel-karavan) 04/04: Preview fixes of new Designer

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 133b002adaaafe08dbac34b489f87985d62bee7b
Author: Marat Gubaidullin <ma...@talismancloud.io>
AuthorDate: Wed Dec 6 21:36:57 2023 -0500

    Preview fixes of new Designer
---
 karavan-space/src/designer/DesignerStore.ts        |  34 +--
 .../src/designer/route/DslConnections.tsx          | 200 ++++++++-------
 karavan-space/src/designer/route/RouteDesigner.tsx |   6 +-
 .../src/designer/route/element/DslElement.css      |  30 ++-
 .../src/designer/route/element/DslElement.tsx      | 265 +++-----------------
 .../designer/route/element/DslElementHeader.tsx    | 275 +++++++++++++++++++++
 .../src/designer/route/useRouteDesignerHook.tsx    |   3 +-
 karavan-space/src/designer/utils/CamelUi.tsx       |   3 +-
 karavan-space/src/designer/utils/EventBus.ts       |  34 +--
 .../src/main/webui/src/designer/DesignerStore.ts   |  34 +--
 .../webui/src/designer/route/DslConnections.tsx    | 200 ++++++++-------
 .../webui/src/designer/route/RouteDesigner.tsx     |   6 +-
 .../src/designer/route/element/DslElement.css      |  30 ++-
 .../src/designer/route/element/DslElement.tsx      | 265 +++-----------------
 .../designer/route/element/DslElementHeader.tsx    | 275 +++++++++++++++++++++
 .../src/designer/route/useRouteDesignerHook.tsx    |   3 +-
 .../src/main/webui/src/designer/utils/CamelUi.tsx  |   3 +-
 .../src/main/webui/src/designer/utils/EventBus.ts  |  34 +--
 18 files changed, 908 insertions(+), 792 deletions(-)

diff --git a/karavan-space/src/designer/DesignerStore.ts b/karavan-space/src/designer/DesignerStore.ts
index f9bc38d1..407713a7 100644
--- a/karavan-space/src/designer/DesignerStore.ts
+++ b/karavan-space/src/designer/DesignerStore.ts
@@ -16,7 +16,7 @@
  */
 
 import {CamelElement, Integration} from "karavan-core/lib/model/IntegrationDefinition";
-import {ButtonPosition, DslPosition, EventBus} from "./utils/EventBus";
+import {DslPosition, EventBus} from "./utils/EventBus";
 import {createWithEqualityFn} from "zustand/traditional";
 import {shallow} from "zustand/shallow";
 
@@ -120,10 +120,6 @@ interface ConnectionsState {
     deleteStep: (uuid: string) => void;
     clearSteps: () => void;
     setSteps: (steps: Map<string, DslPosition>) => void;
-    buttons: ButtonPosition[];
-    addButton: (button: ButtonPosition) => void;
-    deleteButton: (button: ButtonPosition) => void;
-    clearButtons: () => void;
 }
 
 export const useConnectionsStore = createWithEqualityFn<ConnectionsState>((set) => ({
@@ -138,6 +134,8 @@ export const useConnectionsStore = createWithEqualityFn<ConnectionsState>((set)
             // state.steps.clear();
             Array.from(state.steps.entries())
                 .filter(value => value[1]?.parent?.uuid !== uuid)
+                .filter(value => value[1]?.prevStep?.uuid !== uuid)
+                .filter(value => value[1]?.nextstep?.uuid !== uuid)
                 .forEach(value => state.steps.set(value[0], value[1]));
             state.steps.delete(uuid)
             return state;
@@ -151,31 +149,7 @@ export const useConnectionsStore = createWithEqualityFn<ConnectionsState>((set)
     },
     setSteps: (steps: Map<string, DslPosition>) => {
         set({steps: steps})
-    },
-    buttons: [],
-    addButton: (button: ButtonPosition) => {
-        set((state: ConnectionsState) => {
-            const index = state.buttons.findIndex(b => b.uuid === button.uuid);
-            if (index !== -1) {
-                state.buttons.splice(index, 1);
-            }
-            state.buttons.push(button);
-            return state;
-        })
-    },
-    clearButtons: () => {
-        set((state: ConnectionsState) => {
-            state.buttons.length = 0;
-            return state;
-        })
-    },
-    deleteButton: (button: ButtonPosition) => {
-        set((state: ConnectionsState) => {
-            const index = state.buttons.findIndex(b => b.uuid === button.uuid);
-            state.buttons.splice(index, 1);
-            return state;
-        })
-    },
+    }
 }), shallow)
 
 type DesignerState = {
diff --git a/karavan-space/src/designer/route/DslConnections.tsx b/karavan-space/src/designer/route/DslConnections.tsx
index 5f9467b6..c7651aac 100644
--- a/karavan-space/src/designer/route/DslConnections.tsx
+++ b/karavan-space/src/designer/route/DslConnections.tsx
@@ -14,15 +14,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React, {useEffect} from 'react';
+import React, {JSX, useEffect, useState} from 'react';
 import '../karavan.css';
-import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
-import {ButtonPosition, DslPosition, EventBus} from "../utils/EventBus";
+import {DslPosition, EventBus} from "../utils/EventBus";
 import {CamelUi} from "../utils/CamelUi";
 import {useConnectionsStore, useDesignerStore, useIntegrationStore} from "../DesignerStore";
 import {shallow} from "zustand/shallow";
 import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
 import {TopologyUtils} from "karavan-core/lib/api/TopologyUtils";
+import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
+import {v4 as uuidv4} from "uuid";
 
 const overlapGap: number = 40;
 
@@ -31,34 +32,24 @@ export function DslConnections() {
     const [integration] = useIntegrationStore((state) => [state.integration], shallow)
     const [width, height, top, left, hideLogDSL] = useDesignerStore((s) =>
         [s.width, s.height, s.top, s.left, s.hideLogDSL], shallow)
-    const [steps, addStep, deleteStep, clearSteps, buttons, addButton, clearButtons, deleteButton] =
-        useConnectionsStore((s) => [s.steps, s.addStep, s.deleteStep, s.clearSteps,
-            s.buttons, s.addButton, s.clearButtons, s.deleteButton], shallow)
+    const [steps, addStep, deleteStep, clearSteps] =
+        useConnectionsStore((s) => [s.steps, s.addStep, s.deleteStep, s.clearSteps], shallow)
+
+    const [svgKey, setSvgKey] = useState<string>('svgKey');
 
     useEffect(() => {
         const sub1 = EventBus.onPosition()?.subscribe((evt: DslPosition) => setPosition(evt));
-        const sub2 = EventBus.onButtonPosition()?.subscribe((btn: ButtonPosition) => setButtonPosition(btn));
         return () => {
             sub1?.unsubscribe();
-            sub2?.unsubscribe();
         };
     });
 
     useEffect(() => {
-        const toDelete: string[] = Array.from(steps.keys()).filter(k => CamelDefinitionApiExt.findElementInIntegration(integration, k) === undefined);
-        toDelete.forEach(key => deleteStep(key));
+        const toDelete1: string[] = Array.from(steps.keys()).filter(k => CamelDefinitionApiExt.findElementInIntegration(integration, k) === undefined);
+        toDelete1.forEach(key => deleteStep(key));
+        setSvgKey(uuidv4())
     }, [integration]);
 
-    function setButtonPosition(btn: ButtonPosition) {
-        if (btn.command === "add") {
-            addButton(btn);
-        } else if (btn.command === "delete") {
-            deleteButton(btn);
-        } else if (btn.command === "clean") {
-            clearButtons();
-        }
-    }
-
     function setPosition(evt: DslPosition) {
         if (evt.command === "add") {
             addStep(evt.step.uuid, evt);
@@ -103,8 +94,6 @@ export function DslConnections() {
             return (
                 <g key={pos.step.uuid + "-incoming"}>
                     <circle cx={incomingX} cy={fromY} r={r} className="circle-incoming"/>
-                    {/*<image x={imageX} y={imageY} href={CamelUi.getConnectionIconString(pos.step)} className="icon"/>*/}
-                    {/*<text x={imageX - 5} y={imageY + 40} className="caption" textAnchor="start">{CamelUi.getTitle(pos.step)}</text>*/}
                     <path d={`M ${lineX1},${lineY1} C ${lineX1},${lineY2} ${lineX2},${lineY1}  ${lineX2},${lineY2}`}
                           className="path-incoming" markerEnd="url(#arrowhead)"/>
                 </g>
@@ -189,8 +178,6 @@ export function DslConnections() {
             return (
                 <g key={pos.step.uuid + "-outgoing"}>
                     <circle cx={outgoingX} cy={outgoingY} r={r} className="circle-outgoing"/>
-                    {/*<image x={imageX} y={imageY} href={image} className="icon"/>*/}
-                    {/*<text x={imageX + 25} y={imageY + 40}  className="caption" textAnchor="end">{CamelUi.getOutgoingTitle(pos.step)}</text>*/}
                     <path
                         d={`M ${lineX1},${lineY1} C ${lineXi - 20}, ${lineY1} ${lineX1 - 15},${lineYi} ${lineXi},${lineYi} L ${lineX2},${lineY2}`}
                         className="path-incoming" markerEnd="url(#arrowhead)"/>
@@ -226,79 +213,113 @@ export function DslConnections() {
         )
     }
 
-    function hasSteps(step: CamelElement): boolean {
-        return (step.hasSteps() && !['FromDefinition'].includes(step.dslName))
-            || ['RouteDefinition', 'TryDefinition', 'ChoiceDefinition', 'SwitchDefinition'].includes(step.dslName);
+    function getNext(pos: DslPosition): CamelElement | undefined {
+        if (pos.nextstep) {
+            return pos.nextstep;
+        } else if (pos.parent) {
+            const parent = steps.get(pos.parent.uuid);
+            if (parent) return getNext(parent);
+        }
+    }
+
+    function isSpecial(pos: DslPosition): boolean {
+        return ['ChoiceDefinition', 'MulticastDefinition', 'TryDefinition'].includes(pos.step.dslName);
     }
 
-    function getPreviousStep(pos: DslPosition) {
-        return Array.from(steps.values())
-            .filter(p => pos.parent?.uuid === p.parent?.uuid)
-            .filter(p => p.inSteps)
-            .filter(p => p.position === pos.position - 1)[0];
+    function addArrowToList(list: JSX.Element[], from?: DslPosition, to?: DslPosition, fromHeader?: boolean, toHeader?: boolean): JSX.Element[]  {
+        const result: JSX.Element[] = [...list];
+        if (from && to) {
+            const rect1 = fromHeader === true ? from.headerRect : from.rect;
+            const rect2 = toHeader === true ? to.headerRect : to.rect;
+            const key = from.step.uuid + "->" + to.step.uuid;
+            result.push(getComplexArrow(key, rect1, rect2, toHeader === true));
+        }
+        return result;
     }
 
-    function getArrow(pos: DslPosition) {
-        const endX = pos.headerRect.x + pos.headerRect.width / 2 - left;
-        const endY = pos.headerRect.y - 9 - top;
-        if (pos.parent) {
+    function getArrow(pos: DslPosition): JSX.Element[] {
+        const list: JSX.Element[] = [];
+
+         if (pos.parent && pos.parent.dslName === 'TryDefinition' && pos.position === 0) {
+            const parent = steps.get(pos.parent.uuid);
+            list.push(...addArrowToList(list, parent, pos, true, false))
+        } else if (pos.parent && pos.parent.dslName === 'MulticastDefinition') {
+            const parent = steps.get(pos.parent.uuid);
+            list.push(...addArrowToList(list, parent, pos, true, false))
+            if (parent?.nextstep) {
+                const to = steps.get(parent.nextstep.uuid);
+                list.push(...addArrowToList(list, pos, to, true, true))
+            }
+        } else if (pos.parent && pos.parent.dslName === 'ChoiceDefinition') {
             const parent = steps.get(pos.parent.uuid);
-            const showArrow = pos.prevStep !== undefined && !['TryDefinition', 'ChoiceDefinition'].includes(pos.prevStep.dslName);
-            const name = pos.prevStep?.dslName;
-            if (parent && showArrow) {
-                if ((!pos.inSteps || (pos.inSteps && pos.position === 0)) && parent.step.dslName !== 'MulticastDefinition') {
-                    return getArrows(pos);
-                } else if (parent.step.dslName === 'MulticastDefinition' && pos.inSteps) {
-                    return getArrows(pos)
-                } else if (pos.inSteps && pos.position > 0 && !hasSteps(pos.step)) {
-                    const prev = getPreviousStep(pos);
-                    if (prev) {
-                        const r = hasSteps(prev.step) ? prev.rect : prev.headerRect;
-                        const prevX = r.x + r.width / 2 - left;
-                        const prevY = r.y + r.height - top;
-                        return (
-                            <line name={name} x1={prevX} y1={prevY} x2={endX} y2={endY} className="path"
-                                  key={pos.step.uuid} markerEnd="url(#arrowhead)"/>
-                        )
-                    }
-                } else if (pos.inSteps && pos.position > 0 && hasSteps(pos.step)) {
-                    const prev = getPreviousStep(pos);
-                    if (prev) {
-                        const r = hasSteps(prev.step) ? prev.rect : prev.headerRect;
-                        const prevX = r.x + r.width / 2 - left;
-                        const prevY = r.y + r.height - top;
-                        return (
-                            <line name={name} x1={prevX} y1={prevY} x2={endX} y2={endY} className="path"
-                                  key={pos.step.uuid} markerEnd="url(#arrowhead)"/>
-                        )
-                    }
+            list.push(...addArrowToList(list, parent, pos, true, false))
+        } else if (pos.parent && ['WhenDefinition', 'OtherwiseDefinition', 'CatchDefinition', 'FinallyDefinition'].includes(pos.parent.dslName)) {
+            if (pos.position === 0) {
+                const parent = steps.get(pos.parent.uuid);
+                list.push(...addArrowToList(list, parent, pos, true, false))
+            }
+            if (pos.position === (pos.inStepsLength - 1) && !isSpecial(pos)) {
+                const nextElement = getNext(pos);
+                if (nextElement) {
+                    const next = steps.get(nextElement.uuid);
+                    list.push(...addArrowToList(list, pos, next, true, true))
                 }
             }
+        } else if (pos.step && !isSpecial(pos)) {
+            if (pos.nextstep) {
+                const next = steps.get(pos.nextstep.uuid);
+                const fromHeader = !pos.step.hasSteps();
+                list.push(...addArrowToList(list, pos, next, fromHeader, true))
+            }
+            if (pos.step.hasSteps() && (pos.step as any).steps.length > 0) {
+                const firstStep = (pos.step as any).steps[0];
+                const next = steps.get(firstStep.uuid);
+                list.push(...addArrowToList(list, pos, next, true, true))
+            }
         }
-    }
 
-    function getArrows(pos: DslPosition) {
-        if (pos.parent) {
-            const parent = steps.get(pos?.parent.uuid);
-            if (parent) {
-            const rect1 = parent.headerRect;
-            const rect2 = pos.headerRect;
-            return getComplexArrow(pos.step.uuid, rect1, rect2);
+        if (['WhenDefinition', 'OtherwiseDefinition'].includes(pos.step.dslName) && pos.step.hasSteps() && (pos.step as any).steps.length === 0) {
+            if (pos.nextstep) {
+                const to = steps.get(pos.nextstep.uuid);
+                list.push(...addArrowToList(list, pos, to, true, true))
+            } else {
+                const next = getNext(pos);
+                if (next) {
+                    const to = steps.get(next.uuid);
+                    list.push(...addArrowToList(list, pos, to, true, true))
+                }
             }
         }
-    }
 
-    function getButtonArrow(btn: ButtonPosition) {
-        const rect1 = btn.rect;
-        const uuid = btn.nextstep.uuid;
-        const nextStep = steps.get(uuid);
-        const rect2 = nextStep?.rect;
-        if (rect1 && rect2) {
-            return getComplexArrow(uuid, rect1, rect2);
+        if (pos.parent?.dslName === 'TryDefinition' && pos.inSteps && pos.position === (pos.inStepsLength - 1)) {
+            const parent = steps.get(pos.parent.uuid);
+            if (parent && parent.nextstep) {
+                const to = steps.get(parent.nextstep.uuid);
+                list.push(...addArrowToList(list, pos, to, true, true))
+            }
         }
+
+        if (!isSpecial(pos) && pos.inSteps && pos.nextstep && pos.parent?.dslName !== 'MulticastDefinition') {
+            const next = steps.get(pos.nextstep.uuid);
+            if (pos.step.hasSteps() && pos.prevStep) {
+            } else {
+                list.push(...addArrowToList(list, pos, next, true, true))
+            }
+        }
+
+        if (!isSpecial(pos) && pos.inSteps && pos.nextstep && pos.parent?.dslName !== 'MulticastDefinition') {
+            const next = steps.get(pos.nextstep.uuid);
+            if (next && !isSpecial(next) && next.inSteps) {
+                // console.log(pos)
+                // const to = steps.get(parent.nextstep.uuid);
+                // list.push(...addArrowToList(list, pos, to, true, true))
+            }
+        }
+
+        return list;
     }
 
-    function getComplexArrow(key: string, rect1: DOMRect, rect2: DOMRect) {
+    function getComplexArrow(key: string, rect1: DOMRect, rect2: DOMRect, toHeader: boolean) {
             const startX = rect1.x + rect1.width / 2 - left;
             const startY = rect1.y + rect1.height - top - 2;
             const endX = rect2.x + rect2.width / 2 - left;
@@ -309,7 +330,7 @@ export function DslConnections() {
 
             const radX = gapX > 30 ? 20 : gapX/2;
             const radY = gapY > 30 ? 20 : gapY/2;
-            const endY = rect2.y - top - 9 - radY;
+            const endY = rect2.y - top - radY - (toHeader ? 9 : 6);
 
             const iRadX = startX > endX ? -1 * radX : radX;
             const iRadY = startY > endY ? -1 * radY : radY;
@@ -336,28 +357,27 @@ export function DslConnections() {
                 + ` L ${LX2} ${LY2}`
                 + ` Q ${Q2_X1} ${Q2_Y1} ${Q2_X2} ${Q2_Y2}`
             return (
-                <path key={key} d={path} className="path" markerEnd="url(#arrowhead)"/>
+                <path key={uuidv4()} name={key} d={path} className="path" markerEnd="url(#arrowhead)"/>
             )
     }
 
     function getSvg() {
         const stepsArray = Array.from(steps.values());
+        const arrows = stepsArray.map(pos => getArrow(pos)).flat(1);
+        const uniqueArrows = [...new Map(arrows.map(item =>  [(item as any).key, item])).values()]
         return (
-            <svg
+            <svg key={svgKey}
                 style={{width: width, height: height, position: "absolute", left: 0, top: 0}}
                 viewBox={"0 0 " + (width) + " " + (height)}>
                 <defs>
-                    <marker id="arrowhead" markerWidth="9" markerHeight="6" refX="0" refY="3" orient="auto"
-                            className="arrow">
+                    <marker id="arrowhead" markerWidth="9" markerHeight="6" refX="0" refY="3" orient="auto" className="arrow">
                         <polygon points="0 0, 9 3, 0 6"/>
                     </marker>
                 </defs>
                 {stepsArray.map(pos => getCircle(pos))}
-                {stepsArray.map(pos => getArrow(pos))}
-                {buttons.map(btn => getButtonArrow(btn)).filter(b => b !== undefined)}
+                {uniqueArrows}
                 {getIncomings().map(p => getIncoming(p))}
                 {getOutgoings().map(p => getOutgoing(p))}
-                {/*{getInternals().map((p) => getInternalLines(p)).flat()}*/}
             </svg>
         )
     }
diff --git a/karavan-space/src/designer/route/RouteDesigner.tsx b/karavan-space/src/designer/route/RouteDesigner.tsx
index b6b1c942..b02ed20b 100644
--- a/karavan-space/src/designer/route/RouteDesigner.tsx
+++ b/karavan-space/src/designer/route/RouteDesigner.tsx
@@ -147,16 +147,17 @@ export function RouteDesigner() {
                      data-click="FLOWS"
                      onClick={event => {unselectElement(event)}}
                      ref={flowRef}>
-                    {routeConfigurations?.map((routeConfiguration, index: number) => (
+                    {routeConfigurations?.map((routeConfiguration, index: number, array) => (
                         <DslElement key={routeConfiguration.uuid}
                                     inSteps={false}
                                     position={index}
                                     step={routeConfiguration}
                                     nextStep={undefined}
                                     prevStep={undefined}
+                                    inStepsLength={array.length}
                                     parent={undefined}/>
                     ))}
-                    {routes?.map((route: any, index: number) => {
+                    {routes?.map((route: any, index: number, array) => {
                         return (
                             <DslElement key={route.uuid}
                                         inSteps={false}
@@ -164,6 +165,7 @@ export function RouteDesigner() {
                                         step={route}
                                         nextStep={undefined}
                                         prevStep={undefined}
+                                        inStepsLength={array.length}
                                         parent={undefined}/>
                         )
                     })}
diff --git a/karavan-space/src/designer/route/element/DslElement.css b/karavan-space/src/designer/route/element/DslElement.css
index 3039f422..61cc5af3 100644
--- a/karavan-space/src/designer/route/element/DslElement.css
+++ b/karavan-space/src/designer/route/element/DslElement.css
@@ -17,12 +17,24 @@
 
 .karavan .dsl-page .flows .step-element .header-route {
     display: block;
-    border: none;
     background: transparent;
-    padding: 0;
-    margin: 3px 24px 10px 24px;
-    /*min-width: 260px;*/
+    border-radius: 42px;
+    padding: 20px;
+    margin: 0;
     z-index: 101;
+    min-width: 260px;
+}
+
+.karavan .dsl-page .flows .step-element .header-bottom-selected {
+    border-bottom: 1px dashed var(--step-border-color-selected);
+}
+
+.karavan .dsl-page .flows .step-element .header-bottom-not-selected {
+    border-bottom: 1px dashed var(--pf-v5-global--Color--200);
+}
+
+.karavan .dsl-page .flows .step-element .header-route:hover {
+    cursor: pointer;
 }
 
 .karavan .step-element .header-route .delete-button {
@@ -41,7 +53,7 @@
 .karavan .step-element .header .delete-button,
 .element-builder .header .delete-button {
     position: absolute;
-    top: -7px;
+    top: -11px;
     line-height: 1;
     border: 0;
     padding: 0;
@@ -65,10 +77,6 @@
     height: 50px;
 }
 
-.karavan .step-element-selected {
-    background-color: rgba(var(--pf-v5-global--palette--blue-50), 1);
-}
-
 .karavan .step-element .selected .header-icon {
     border-color: var(--pf-v5-global--primary-color--100);
     background-color: var(--pf-v5-global--palette--blue-50);
@@ -145,7 +153,7 @@
 
 .karavan .step-element .insert-element-button {
     position: absolute;
-    top: -7px;
+    top: -11px;
     line-height: 1;
     border: 0;
     padding: 0;
@@ -214,4 +222,4 @@
     width: 20px;
     height: 20px;
     background: white;
-}
\ No newline at end of file
+}
diff --git a/karavan-space/src/designer/route/element/DslElement.tsx b/karavan-space/src/designer/route/element/DslElement.tsx
index 1deb46da..967d3321 100644
--- a/karavan-space/src/designer/route/element/DslElement.tsx
+++ b/karavan-space/src/designer/route/element/DslElement.tsx
@@ -14,20 +14,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React, {CSSProperties, useMemo, useState} from 'react';
-import {Text, Tooltip,} from '@patternfly/react-core';
+import React, {CSSProperties, useState} from 'react';
+import {Tooltip,} from '@patternfly/react-core';
 import '../../karavan.css';
 import './DslElement.css';
 import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
-import {CamelUi} from "../../utils/CamelUi";
 import {EventBus} from "../../utils/EventBus";
 import {ChildElement, CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
-import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
 import {CamelDisplayUtil} from "karavan-core/lib/api/CamelDisplayUtil";
 import {useDesignerStore, useIntegrationStore} from "../../DesignerStore";
 import {shallow} from "zustand/shallow";
 import {useRouteDesignerHook} from "../useRouteDesignerHook";
-import {AddElementIcon, DeleteElementIcon, InsertElementIcon} from "./DslElementIcons";
+import {AddElementIcon} from "./DslElementIcons";
+import {DslElementHeader} from "./DslElementHeader";
 
 interface Props {
     step: CamelElement,
@@ -36,12 +35,12 @@ interface Props {
     prevStep: CamelElement | undefined,
     inSteps: boolean
     position: number
+    inStepsLength: number
 }
 
 export function DslElement(props: Props) {
 
     const headerRef = React.useRef<HTMLDivElement>(null);
-    const addButtonRef = React.useRef<HTMLDivElement>(null);
     const {
         selectElement,
         moveElement,
@@ -70,11 +69,6 @@ export function DslElement(props: Props) {
         }
     }
 
-    function onDeleteElement(evt: React.MouseEvent) {
-        evt.stopPropagation();
-        onShowDeleteConfirmation(props.step.uuid);
-    }
-
     function onSelectElement(evt: React.MouseEvent) {
         evt.stopPropagation();
         selectElement(props.step);
@@ -100,18 +94,15 @@ export function DslElement(props: Props) {
         return selectedUuids.includes(props.step.uuid);
     }
 
-    function isElementHidden(): boolean {
-        return props.step.dslName === 'LogDefinition' && hideLogDSL;
-    }
-
     function hasBorder(): boolean {
         const step = props.step;
-        if (['FilterDefinition'].includes(step.dslName)) {
+        if (['FilterDefinition', 'RouteDefinition', 'RouteConfigurationDefinition'].includes(step.dslName)) {
             return true;
         }
-        if (['FromDefinition',
-            'RouteDefinition',
+        if ([
+            'FromDefinition',
             'TryDefinition',
+            'MulticastDefinition',
             'CatchDefinition', 'FinallyDefinition',
             'ChoiceDefinition',
             'SwitchDefinition', 'WhenDefinition', 'OtherwiseDefinition'
@@ -125,11 +116,6 @@ export function DslElement(props: Props) {
         return ['FromDefinition', 'RouteConfigurationDefinition', 'RouteDefinition', 'WhenDefinition', 'OtherwiseDefinition'].includes(props.step.dslName);
     }
 
-    function isWide(): boolean {
-        return ['RouteConfigurationDefinition', 'RouteDefinition', 'ChoiceDefinition', 'SwitchDefinition', 'MulticastDefinition', 'TryDefinition', 'CircuitBreakerDefinition']
-            .includes(props.step.dslName);
-    }
-
     function isAddStepButtonLeft(): boolean {
         return ['MulticastDefinition']
             .includes(props.step.dslName);
@@ -139,9 +125,6 @@ export function DslElement(props: Props) {
         return ['MulticastDefinition'].includes(props.step.dslName);
     }
 
-    function isRoot(): boolean {
-        return ['RouteConfigurationDefinition', 'RouteDefinition'].includes(props.step?.dslName);
-    }
 
     function isInStepWithChildren() {
         const step: CamelElement = props.step;
@@ -149,71 +132,10 @@ export function DslElement(props: Props) {
         return children.filter((c: ChildElement) => c.name === 'steps' || c.multiple).length > 0 && props.inSteps;
     }
 
-    function getChildrenInfo(step: CamelElement): [boolean, number, boolean, number, number] {
-        const children = CamelDefinitionApiExt.getElementChildrenDefinition(step.dslName);
-        const hasStepsField = children.filter((c: ChildElement) => c.name === 'steps').length === 1;
-        const stepsChildrenCount = children
-            .filter(c => c.name === 'steps')
-            .map((child: ChildElement, index: number) => {
-                const children: CamelElement[] = CamelDefinitionApiExt.getElementChildren(step, child);
-                return children.length;
-            }).reduce((a, b) => a + b, 0);
-
-        const hasNonStepsFields = children.filter(c => c.name !== 'steps' && c.name !== 'expression' && c.name !== 'onWhen').length > 0;
-        const childrenCount = children
-            .map((child: ChildElement, index: number) => {
-                const children: CamelElement[] = CamelDefinitionApiExt.getElementChildren(step, child);
-                return children.length;
-            }).reduce((a, b) => a + b, 0);
-        const nonStepChildrenCount = childrenCount - stepsChildrenCount;
-        return [hasStepsField, stepsChildrenCount, hasNonStepsFields, nonStepChildrenCount, childrenCount]
-    }
-
-    function hasWideChildrenElement() {
-        const [hasStepsField, stepsChildrenCount, hasNonStepsFields, nonStepChildrenCount, childrenCount] = getChildrenInfo(props.step);
-        if (isHorizontal() && stepsChildrenCount > 1) return true;
-        else if (hasStepsField && stepsChildrenCount > 0 && hasNonStepsFields && nonStepChildrenCount > 0) return true;
-        else if (!hasStepsField && hasNonStepsFields && childrenCount > 1) return true;
-        else if (hasStepsField && stepsChildrenCount > 0 && hasNonStepsFields && childrenCount > 1) return true;
-        else return false;
-    }
-
-    function hasBorderOverSteps(step: CamelElement) {
-        const [hasStepsField, stepsChildrenCount, hasNonStepsFields, nonStepChildrenCount] = getChildrenInfo(step);
-        if (hasStepsField && stepsChildrenCount > 0 && hasNonStepsFields && nonStepChildrenCount > 0) return true;
-        else return false;
-    }
-
-    function getHeaderStyle() {
-        const style: CSSProperties = {
-            width: isWide() ? "100%" : "",
-            fontWeight: isElementSelected() ? "bold" : "normal",
-        };
-        return style;
-    }
-
-    function sendButtonPosition(el: HTMLButtonElement | null) {
-        const {nextStep, step, parent} = props;
-        let needArrow = !hasBorder() && !['ChoiceDefinition', 'MulticastDefinition', 'TryDefinition'].includes(step.dslName);
-
-        if (parent
-            && ['TryDefinition'].includes(parent.dslName)
-            && !['CatchDefinition', 'FinallyDefinition'].includes(step.dslName)) {
-            needArrow = true;
-        }
-
-        if (el && nextStep && needArrow) {
-            const rect = headerRef.current?.getBoundingClientRect();
-
-            if (rect)
-                EventBus.sendButtonPosition("add", step.uuid, nextStep, rect);
-        }
-    }
 
     function sendPosition(el: HTMLDivElement | null) {
-        const {step, prevStep, parent} = props;
+        const {step, prevStep, nextStep, parent, inSteps, inStepsLength} = props;
         const isSelected = isElementSelected();
-        const isHidden = isElementHidden();
         if (el) {
             const header = Array.from(el.childNodes.values()).filter((n: any) => n.classList.contains("header"))[0];
             if (header) {
@@ -221,109 +143,14 @@ export function DslElement(props: Props) {
                 const headerRect = headerIcon.getBoundingClientRect();
                 const rect = el.getBoundingClientRect();
                 if (step.showChildren) {
-                    if (isHidden) {
-                        EventBus.sendPosition("add", step, prevStep, parent, rect, headerRect, props.position, props.inSteps, isSelected);
-                    } else {
-                        EventBus.sendPosition("add", step, prevStep, parent, rect, headerRect, props.position, props.inSteps, isSelected);
-                    }
-                } else {
-                    EventBus.sendPosition("delete", step, prevStep, parent, new DOMRect(), new DOMRect(), 0);
+                    EventBus.sendPosition("add", step, prevStep, nextStep, parent, rect, headerRect, props.position, inStepsLength, inSteps, isSelected);
                 }
             }
-        }
-    }
-
-    function getAvailableModels() { // TODO: make static list-of-values instead
-        const step: CamelElement = props.step
-        return CamelUi.getSelectorModelsForParent(step.dslName, false);
-    }
-
-    const availableModels = useMemo(
-        () => getAvailableModels(),
-        [props.step.dslName]
-    );
-
-
-    function getHeader() {
-        const step: CamelElement = props.step;
-        const parent = props.parent;
-        const inRouteConfiguration = parent !== undefined && parent.dslName === 'RouteConfigurationDefinition';
-        const showAddButton = !['CatchDefinition', 'RouteDefinition'].includes(step.dslName) && availableModels.length > 0;
-        const showInsertButton =
-            !['FromDefinition', 'RouteConfigurationDefinition', 'RouteDefinition', 'CatchDefinition', 'FinallyDefinition', 'WhenDefinition', 'OtherwiseDefinition'].includes(step.dslName)
-            && !inRouteConfiguration;
-        const headerClass = ['RouteConfigurationDefinition', 'RouteDefinition'].includes(step.dslName) ? "header-route" : "header"
-        const headerClasses = isElementSelected() ? headerClass + " selected" : headerClass;
-        return (
-            <div className={"dsl-element " + headerClasses} style={getHeaderStyle()} ref={headerRef}>
-                {!['RouteConfigurationDefinition', 'RouteDefinition'].includes(props.step.dslName) &&
-                    <div
-                        ref={el => sendPosition(el)}
-                        className={"header-icon"}
-                        style={isWide() ? {width: ""} : {}}>
-                        {CamelUi.getIconForElement(step)}
-                    </div>
-                }
-                <div className={hasWideChildrenElement() ? "header-text" : ""}>
-                    {hasWideChildrenElement() && <div className="spacer"/>}
-                    {getHeaderTextWithTooltip(step)}
-                </div>
-                {showInsertButton && getInsertElementButton()}
-                {getDeleteButton()}
-                {showAddButton && getAddElementButton()}
-            </div>
-        )
-    }
-
-    function getHeaderText(step: CamelElement): string {
-        if (isKamelet() && step.dslName === 'ToDefinition' && (step as any).uri === 'kamelet:sink') {
-            return "Sink";
-        } else if (isKamelet() && step.dslName === 'FromDefinition' && (step as any).uri === 'kamelet:source') {
-            return "Source";
         } else {
-            return (step as any).description ? (step as any).description : CamelUi.getElementTitle(props.step);
+            EventBus.sendPosition("delete", step, prevStep, nextStep, parent, new DOMRect(), new DOMRect(), 0, 0);
         }
     }
 
-    function getHeaderTextWithTooltip(step: CamelElement) {
-        const checkRequired = CamelUtil.checkRequired(step);
-        const title = getHeaderText(step);
-        let className = hasWideChildrenElement() ? "text text-right" : "text text-bottom";
-        if (!checkRequired[0]) className = className + " header-text-required";
-        if (checkRequired[0]) {
-            return <Text className={className}>{title}</Text>
-        } else return (
-            <Tooltip position={"right"} className="tooltip-required-field"
-                     content={checkRequired[1].map((text, i) => (<div key={i}>{text}</div>))}>
-                <Text className={className}>{title}</Text>
-            </Tooltip>
-        )
-    }
-
-    function getHeaderWithTooltip(tooltip: string | undefined) {
-        return (
-            <>
-                {getHeader()}
-                <Tooltip triggerRef={headerRef} position={"left"} content={<div>{tooltip}</div>}/>
-            </>
-
-        )
-    }
-
-    function getHeaderTooltip(): string | undefined {
-        if (CamelUi.isShowExpressionTooltip(props.step)) return CamelUi.getExpressionTooltip(props.step);
-        if (CamelUi.isShowUriTooltip(props.step)) return CamelUi.getUriTooltip(props.step);
-        return undefined;
-    }
-
-    function getElementHeader() {
-        const tooltip = getHeaderTooltip();
-        if (tooltip !== undefined && !isDragging) {
-            return getHeaderWithTooltip(tooltip);
-        }
-        return getHeader();
-    }
-
     function getChildrenStyle() {
         const style: CSSProperties = {
             display: "flex",
@@ -333,10 +160,7 @@ export function DslElement(props: Props) {
     }
 
     function getChildrenElementsStyle(child: ChildElement, notOnlySteps: boolean) {
-        const step = props.step;
-        const isBorder = child.name === 'steps' && hasBorderOverSteps(step);
         const style: CSSProperties = {
-            // borderStyle: isBorder ? "dotted" : "none",
             borderColor: "var(--step-border-color)",
             borderWidth: "1px",
             borderRadius: "16px",
@@ -375,10 +199,12 @@ export function DslElement(props: Props) {
             return (
                 <div className={child.name + " has-child"} style={getChildrenElementsStyle(child, notOnlySteps)}
                      key={step.uuid + "-child-" + index}>
-                    {children.map((element, index) => {
+                    {children.map((element, index, array) => {
                             let prevStep = children.at(index - 1);
-                            let nextStep = undefined;
-                            if (['TryDefinition', 'ChoiceDefinition'].includes(step.dslName)) {
+                            let nextStep: CamelElement | undefined = undefined;
+                            if ('ChoiceDefinition' === step.dslName) {
+                                nextStep = props.nextStep;
+                            } else if ('TryDefinition' === step.dslName && ['CatchDefinition', 'FinallyDefinition'].includes(element.dslName)) {
                                 nextStep = props.nextStep;
                             } else {
                                 nextStep = children.at(index + 1);
@@ -390,6 +216,7 @@ export function DslElement(props: Props) {
                                     step={element}
                                     nextStep={nextStep}
                                     prevStep={prevStep}
+                                    inStepsLength={array.length}
                                     parent={step}/>
                             </div>)
                         }
@@ -408,16 +235,14 @@ export function DslElement(props: Props) {
     }
 
     function getAddStepButton() {
-        const {step, nextStep} = props;
+        const {step} = props;
         const hideAddButton = step.dslName === 'StepDefinition' && !CamelDisplayUtil.isStepDefinitionExpanded(integration, step.uuid, selectedUuids.at(0));
         if (hideAddButton) return (<></>)
         else return (
-            <div ref={addButtonRef}>
-                <Tooltip position={"bottom"}
+                <Tooltip position={"left"}
                          content={<div>{"Add step to " + CamelDisplayUtil.getTitle(step)}</div>}
                 >
                     <button type="button"
-                            ref={el => sendButtonPosition(el)}
                             aria-label="Add"
                             onClick={e => onOpenSelector(e)}
                             className={isAddStepButtonLeft() ? "add-button add-button-left" : "add-button add-button-bottom"}>
@@ -425,51 +250,12 @@ export function DslElement(props: Props) {
                     </button>
 
                 </Tooltip>
-            </div>
-        )
-    }
-
-    function getAddElementButton() {
-        return (
-            <Tooltip position={"bottom"}
-                     content={<div>{"Add DSL element to " + CamelDisplayUtil.getTitle(props.step)}</div>}>
-                <button
-                    type="button"
-                    aria-label="Add"
-                    onClick={e => onOpenSelector(e, false)}
-                    className={"add-element-button"}>
-                    <AddElementIcon/>
-                </button>
-            </Tooltip>
-        )
-    }
-
-    function getInsertElementButton() {
-        return (
-            <Tooltip position={"left"} content={<div>{"Insert element before"}</div>}>
-                <button type="button"
-                        aria-label="Insert"
-                        onClick={e => onOpenSelector(e, true, true)}
-                        className={"insert-element-button"}>
-                    <InsertElementIcon/>
-                </button>
-            </Tooltip>
-        )
-    }
-
-    function getDeleteButton() {
-        return (
-            <Tooltip position={"right"} content={<div>{"Delete element"}</div>}>
-                <button type="button" aria-label="Delete" onClick={e => onDeleteElement(e)} className="delete-button">
-                    <DeleteElementIcon/>
-                </button>
-            </Tooltip>
         )
     }
 
     const element: CamelElement = props.step;
     const className = "step-element"
-        + (isElementSelected() ? " step-element-selected" : "") + (!props.step.showChildren ? " hidden-step" : "")
+        + (!props.step.showChildren ? " hidden-step" : "")
         + ((element as any).disabled ? " disabled " : "");
     return (
         <div key={"root" + element.uuid}
@@ -516,7 +302,14 @@ export function DslElement(props: Props) {
              onDrop={event => dragElement(event, element)}
              draggable={!isNotDraggable()}
         >
-            {getElementHeader()}
+            <DslElementHeader headerRef={headerRef}
+                              step={props.step}
+                              parent={props.parent}
+                              nextStep={props.nextStep}
+                              prevStep={props.prevStep}
+                              inSteps={props.inSteps}
+                              isDragging={isDragging}
+                              position={props.position}/>
             {getChildElements()}
         </div>
     )
diff --git a/karavan-space/src/designer/route/element/DslElementHeader.tsx b/karavan-space/src/designer/route/element/DslElementHeader.tsx
new file mode 100644
index 00000000..d201d105
--- /dev/null
+++ b/karavan-space/src/designer/route/element/DslElementHeader.tsx
@@ -0,0 +1,275 @@
+/*
+ * 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, {CSSProperties, useMemo} from 'react';
+import {Text, Tooltip,} from '@patternfly/react-core';
+import '../../karavan.css';
+import './DslElement.css';
+import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
+import {CamelUi} from "../../utils/CamelUi";
+import {ChildElement, CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
+import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
+import {CamelDisplayUtil} from "karavan-core/lib/api/CamelDisplayUtil";
+import {useDesignerStore} from "../../DesignerStore";
+import {shallow} from "zustand/shallow";
+import {useRouteDesignerHook} from "../useRouteDesignerHook";
+import {AddElementIcon, DeleteElementIcon, InsertElementIcon} from "./DslElementIcons";
+import { RouteConfigurationDefinition} from "karavan-core/lib/model/CamelDefinition";
+
+interface Props {
+    headerRef: React.RefObject<HTMLDivElement>
+    step: CamelElement,
+    parent: CamelElement | undefined,
+    nextStep: CamelElement | undefined,
+    prevStep: CamelElement | undefined,
+    inSteps: boolean
+    position: number
+    isDragging: boolean
+}
+
+export function DslElementHeader(props: Props) {
+
+    const {
+        selectElement,
+        moveElement,
+        onShowDeleteConfirmation,
+        openSelector,
+        isKamelet,
+        isSourceKamelet,
+        isActionKamelet
+    } = useRouteDesignerHook();
+
+    const [selectedUuids, selectedStep, showMoveConfirmation, setShowMoveConfirmation, hideLogDSL, setMoveElements] =
+        useDesignerStore((s) =>
+            [s.selectedUuids, s.selectedStep, s.showMoveConfirmation, s.setShowMoveConfirmation, s.hideLogDSL, s.setMoveElements], shallow)
+
+    function onOpenSelector(evt: React.MouseEvent, showSteps: boolean = true, isInsert: boolean = false) {
+        evt.stopPropagation();
+        if (isInsert && props.parent) {
+            openSelector(props.parent.uuid, props.parent.dslName, showSteps, props.position);
+        } else {
+            openSelector(props.step.uuid, props.step.dslName, showSteps);
+        }
+    }
+
+    function onDeleteElement(evt: React.MouseEvent) {
+        evt.stopPropagation();
+        onShowDeleteConfirmation(props.step.uuid);
+    }
+
+    function isElementSelected(): boolean {
+        return selectedUuids.includes(props.step.uuid);
+    }
+
+    function isWide(): boolean {
+        return ['RouteConfigurationDefinition', 'RouteDefinition', 'ChoiceDefinition', 'MulticastDefinition', 'TryDefinition', 'CircuitBreakerDefinition']
+            .includes(props.step.dslName);
+    }
+
+    function isHorizontal(): boolean {
+        return ['MulticastDefinition'].includes(props.step.dslName);
+    }
+
+    function getChildrenInfo(step: CamelElement): [boolean, number, boolean, number, number] {
+        const children = CamelDefinitionApiExt.getElementChildrenDefinition(step.dslName);
+        const hasStepsField = children.filter((c: ChildElement) => c.name === 'steps').length === 1;
+        const stepsChildrenCount = children
+            .filter(c => c.name === 'steps')
+            .map((child: ChildElement, index: number) => {
+                const children: CamelElement[] = CamelDefinitionApiExt.getElementChildren(step, child);
+                return children.length;
+            }).reduce((a, b) => a + b, 0);
+
+        const hasNonStepsFields = children.filter(c => c.name !== 'steps' && c.name !== 'expression' && c.name !== 'onWhen').length > 0;
+        const childrenCount = children
+            .map((child: ChildElement, index: number) => {
+                const children: CamelElement[] = CamelDefinitionApiExt.getElementChildren(step, child);
+                return children.length;
+            }).reduce((a, b) => a + b, 0);
+        const nonStepChildrenCount = childrenCount - stepsChildrenCount;
+        return [hasStepsField, stepsChildrenCount, hasNonStepsFields, nonStepChildrenCount, childrenCount]
+    }
+
+    function hasWideChildrenElement() {
+        const [hasStepsField, stepsChildrenCount, hasNonStepsFields, nonStepChildrenCount, childrenCount] = getChildrenInfo(props.step);
+        if (isHorizontal() && stepsChildrenCount > 1) return true;
+        else if (hasStepsField && stepsChildrenCount > 0 && hasNonStepsFields && nonStepChildrenCount > 0) return true;
+        else if (!hasStepsField && hasNonStepsFields && childrenCount > 1) return true;
+        else if (hasStepsField && stepsChildrenCount > 0 && hasNonStepsFields && childrenCount > 1) return true;
+        else return false;
+    }
+
+    function getHeaderStyle() {
+        const style: CSSProperties = {
+            width: isWide() ? "100%" : "",
+            fontWeight: isElementSelected() ? "bold" : "normal",
+        };
+        return style;
+    }
+
+    function getAvailableModels() { // TODO: make static list-of-values instead
+        const step: CamelElement = props.step
+        return CamelUi.getSelectorModelsForParent(step.dslName, false);
+    }
+
+    const availableModels = useMemo(
+        () => getAvailableModels(),
+        [props.step.dslName]
+    );
+
+    function hasElements(rc: RouteConfigurationDefinition): boolean {
+        return (rc.interceptFrom !== undefined && rc.interceptFrom.length > 0)
+    || (rc.intercept !== undefined && rc.intercept.length > 0)
+    || (rc.interceptSendToEndpoint !== undefined && rc.interceptSendToEndpoint.length > 0)
+    || (rc.onException !== undefined && rc.onException.length > 0)
+    || (rc.onCompletion !== undefined && rc.onCompletion.length > 0)
+    }
+
+    function getHeaderClasses(): string {
+        const classes: string[] = [];
+        const step: CamelElement = props.step;
+        if (step.dslName === 'RouteDefinition') {
+            classes.push('header-route')
+            classes.push('header-bottom-line')
+            classes.push(isElementSelected() ? 'header-bottom-selected' : 'header-bottom-not-selected')
+        } else if (step.dslName === 'RouteConfigurationDefinition') {
+            classes.push('header-route')
+            if (hasElements(step)) classes.push('header-bottom-line')
+            classes.push(isElementSelected() ? 'header-bottom-selected' : 'header-bottom-not-selected')
+        } else {
+            classes.push('header')
+        }
+        if (isElementSelected()) {
+            classes.push("selected")
+        }
+        return classes.join(" ");
+    }
+
+    function getHeader() {
+        const step: CamelElement = props.step;
+        const parent = props.parent;
+        const inRouteConfiguration = parent !== undefined && parent.dslName === 'RouteConfigurationDefinition';
+        const showAddButton = !['CatchDefinition', 'RouteDefinition'].includes(step.dslName) && availableModels.length > 0;
+        const showInsertButton =
+            !['FromDefinition', 'RouteConfigurationDefinition', 'RouteDefinition', 'CatchDefinition', 'FinallyDefinition', 'WhenDefinition', 'OtherwiseDefinition'].includes(step.dslName)
+            && !inRouteConfiguration;
+        const headerClasses = getHeaderClasses();
+        return (
+            <div className={"dsl-element " + headerClasses} style={getHeaderStyle()} ref={props.headerRef}>
+                {!['RouteConfigurationDefinition', 'RouteDefinition'].includes(props.step.dslName) &&
+                    <div
+                        className={"header-icon"}
+                        style={isWide() ? {width: ""} : {}}>
+                        {CamelUi.getIconForElement(step)}
+                    </div>
+                }
+                <div className={hasWideChildrenElement() ? "header-text" : ""}>
+                    {hasWideChildrenElement() && <div className="spacer"/>}
+                    {getHeaderTextWithTooltip(step)}
+                </div>
+                {showInsertButton && getInsertElementButton()}
+                {getDeleteButton()}
+                {showAddButton && getAddElementButton()}
+            </div>
+        )
+    }
+
+    function getHeaderText(step: CamelElement): string {
+        if (isKamelet() && step.dslName === 'ToDefinition' && (step as any).uri === 'kamelet:sink') {
+            return "Sink";
+        } else if (isKamelet() && step.dslName === 'FromDefinition' && (step as any).uri === 'kamelet:source') {
+            return "Source";
+        } else {
+            return (step as any).description ? (step as any).description : CamelUi.getElementTitle(props.step);
+        }
+    }
+
+    function getHeaderTextWithTooltip(step: CamelElement) {
+        const title = getHeaderText(step);
+        const checkRequired = CamelUtil.checkRequired(step);
+        let className = hasWideChildrenElement() ? "text text-right" : "text text-bottom";
+        if (!checkRequired[0]) className = className + " header-text-required";
+        if (checkRequired[0]) {
+            return <Text className={className}>{title}</Text>
+        } else return (
+            <Tooltip position={"right"} className="tooltip-required-field"
+                     content={checkRequired[1].map((text, i) => (<div key={i}>{text}</div>))}>
+                <Text className={className}>{title}</Text>
+            </Tooltip>
+        )
+    }
+
+    function getHeaderWithTooltip(tooltip: string | undefined) {
+        return (
+            <>
+                {getHeader()}
+                <Tooltip triggerRef={props.headerRef} position={"left"} content={<div>{tooltip}</div>}/>
+            </>
+
+        )
+    }
+
+    function getHeaderTooltip(): string | undefined {
+        if (CamelUi.isShowExpressionTooltip(props.step)) return CamelUi.getExpressionTooltip(props.step);
+        if (CamelUi.isShowUriTooltip(props.step)) return CamelUi.getUriTooltip(props.step);
+        return undefined;
+    }
+
+
+    function getAddElementButton() {
+        return (
+            <Tooltip position={"bottom"}
+                     content={<div>{"Add DSL element to " + CamelDisplayUtil.getTitle(props.step)}</div>}>
+                <button
+                    type="button"
+                    aria-label="Add"
+                    onClick={e => onOpenSelector(e, false)}
+                    className={"add-element-button"}>
+                    <AddElementIcon/>
+                </button>
+            </Tooltip>
+        )
+    }
+
+    function getInsertElementButton() {
+        return (
+            <Tooltip position={"left"} content={<div>{"Insert element before"}</div>}>
+                <button type="button"
+                        aria-label="Insert"
+                        onClick={e => onOpenSelector(e, true, true)}
+                        className={"insert-element-button"}>
+                    <InsertElementIcon/>
+                </button>
+            </Tooltip>
+        )
+    }
+
+    function getDeleteButton() {
+        return (
+            <Tooltip position={"right"} content={<div>{"Delete element"}</div>}>
+                <button type="button" aria-label="Delete" onClick={e => onDeleteElement(e)} className="delete-button">
+                    <DeleteElementIcon/>
+                </button>
+            </Tooltip>
+        )
+    }
+
+    const tooltip = getHeaderTooltip();
+    if (tooltip !== undefined && !props.isDragging) {
+        return getHeaderWithTooltip(tooltip);
+    }
+    return getHeader();
+}
diff --git a/karavan-space/src/designer/route/useRouteDesignerHook.tsx b/karavan-space/src/designer/route/useRouteDesignerHook.tsx
index b65966a0..1457adba 100644
--- a/karavan-space/src/designer/route/useRouteDesignerHook.tsx
+++ b/karavan-space/src/designer/route/useRouteDesignerHook.tsx
@@ -105,8 +105,7 @@ export function useRouteDesignerHook () {
     }
 
     const deleteElement = () =>  {
-        EventBus.sendPosition("clean", new CamelElement(""), undefined, undefined, new DOMRect(), new DOMRect(), 0);
-        EventBus.sendButtonPosition("clean", '',  new CamelElement(""), new DOMRect());
+        EventBus.sendPosition("clean", new CamelElement(""), undefined,undefined, undefined, new DOMRect(), new DOMRect(), 0, 0);
         let i = integration;
         selectedUuids.forEach(uuidToDelete => {
              i = CamelDefinitionApiExt.deleteStepFromIntegration(i, uuidToDelete);
diff --git a/karavan-space/src/designer/utils/CamelUi.tsx b/karavan-space/src/designer/utils/CamelUi.tsx
index 61a7d27c..5caae0a7 100644
--- a/karavan-space/src/designer/utils/CamelUi.tsx
+++ b/karavan-space/src/designer/utils/CamelUi.tsx
@@ -337,7 +337,7 @@ export class CamelUi {
     static getElementTitle = (element: CamelElement): string => {
         if (element.dslName === 'RouteDefinition') {
             const routeId = (element as RouteDefinition).id
-            return routeId ? "Route: " + routeId : CamelUtil.capitalizeName((element as any).stepName);
+            return routeId ? routeId : CamelUtil.capitalizeName((element as any).stepName);
         } else if (['ToDefinition', 'ToDynamicDefinition', 'FromDefinition', 'KameletDefinition'].includes(element.dslName) && (element as any).uri) {
             const uri = (element as any).uri;
             const kameletTitle = uri && uri.startsWith("kamelet:") ? KameletApi.findKameletByUri(uri)?.title() : undefined;
@@ -787,4 +787,5 @@ export class CamelUi {
             .forEach((f: any) => result.push(f));
         return result;
     }
+
 }
\ No newline at end of file
diff --git a/karavan-space/src/designer/utils/EventBus.ts b/karavan-space/src/designer/utils/EventBus.ts
index 55906f0f..687bec9d 100644
--- a/karavan-space/src/designer/utils/EventBus.ts
+++ b/karavan-space/src/designer/utils/EventBus.ts
@@ -18,30 +18,15 @@ import {Subject} from 'rxjs';
 import {CamelElement, Integration} from "karavan-core/lib/model/IntegrationDefinition";
 import {v4 as uuidv4} from "uuid";
 
-export class ButtonPosition {
-    uuid: string = '';
-    nextstep: CamelElement = new CamelElement("");
-    rect: DOMRect = new DOMRect();
-    command: "add" | "delete" | "clean" = "add";
-
-    constructor(command: "add" | "delete" | "clean",
-                uuid: string,
-                nextstep: CamelElement,
-                rect: DOMRect) {
-        this.uuid = uuid;
-        this.command = command;
-        this.nextstep = nextstep;
-        this.rect = rect;
-    }
-}
-
 export class DslPosition {
     step: CamelElement = new CamelElement("");
     prevStep: CamelElement | undefined;
+    nextstep: CamelElement | undefined;
     parent: CamelElement | undefined;
     inSteps: boolean = false;
     isSelected: boolean = false;
     position: number = 0;
+    inStepsLength: number = 0;
     rect: DOMRect = new DOMRect();
     headerRect: DOMRect = new DOMRect();
     command: "add" | "delete" | "clean" = "add";
@@ -49,20 +34,24 @@ export class DslPosition {
     constructor(command: "add" | "delete" | "clean",
                 step: CamelElement,
                 prevStep: CamelElement | undefined,
+                nextstep: CamelElement | undefined,
                 parent:CamelElement | undefined,
                 rect: DOMRect,
                 headerRect:DOMRect,
                 position: number,
+                inStepsLength: number,
                 inSteps: boolean = false,
                 isSelected: boolean = false) {
         this.command = command;
         this.step = step;
+        this.nextstep = nextstep;
         this.prevStep = prevStep;
         this.parent = parent;
         this.rect = rect;
         this.headerRect = headerRect;
         this.inSteps = inSteps;
         this.position = position;
+        this.inStepsLength = inStepsLength;
         this.isSelected = isSelected;
     }
 }
@@ -104,25 +93,22 @@ export class ToastMessage {
     }
 }
 const dslPositions = new Subject<DslPosition>();
-const buttonPositions = new Subject<ButtonPosition>();
 
 export const EventBus = {
     sendPosition: (command: "add" | "delete" | "clean",
                    step: CamelElement,
                    prevStep: CamelElement | undefined,
+                   nextstep: CamelElement | undefined,
                    parent: CamelElement | undefined,
                    rect: DOMRect,
                    headerRect: DOMRect,
                    position: number,
+                   inStepsLength: number,
                    inSteps: boolean = false,
-                   isSelected: boolean = false) => dslPositions.next(new DslPosition(command, step, prevStep, parent, rect, headerRect, position, inSteps, isSelected)),
+                   isSelected: boolean = false) => dslPositions.next(
+                       new DslPosition(command, step, prevStep, nextstep, parent, rect, headerRect, position, inStepsLength, inSteps, isSelected)),
     onPosition: () => dslPositions.asObservable(),
 
-    sendButtonPosition: (command: "add" | "delete" | "clean", uuid: string,
-                   nextStep: CamelElement,
-                   rect: DOMRect) => buttonPositions.next(new ButtonPosition(command, uuid, nextStep, rect)),
-    onButtonPosition: () => buttonPositions.asObservable(),
-
     sendIntegrationUpdate: (i: Integration, propertyOnly: boolean) => updates.next(new IntegrationUpdate(i, propertyOnly)),
     onIntegrationUpdate: () => updates.asObservable(),
 
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 f9bc38d1..407713a7 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
@@ -16,7 +16,7 @@
  */
 
 import {CamelElement, Integration} from "karavan-core/lib/model/IntegrationDefinition";
-import {ButtonPosition, DslPosition, EventBus} from "./utils/EventBus";
+import {DslPosition, EventBus} from "./utils/EventBus";
 import {createWithEqualityFn} from "zustand/traditional";
 import {shallow} from "zustand/shallow";
 
@@ -120,10 +120,6 @@ interface ConnectionsState {
     deleteStep: (uuid: string) => void;
     clearSteps: () => void;
     setSteps: (steps: Map<string, DslPosition>) => void;
-    buttons: ButtonPosition[];
-    addButton: (button: ButtonPosition) => void;
-    deleteButton: (button: ButtonPosition) => void;
-    clearButtons: () => void;
 }
 
 export const useConnectionsStore = createWithEqualityFn<ConnectionsState>((set) => ({
@@ -138,6 +134,8 @@ export const useConnectionsStore = createWithEqualityFn<ConnectionsState>((set)
             // state.steps.clear();
             Array.from(state.steps.entries())
                 .filter(value => value[1]?.parent?.uuid !== uuid)
+                .filter(value => value[1]?.prevStep?.uuid !== uuid)
+                .filter(value => value[1]?.nextstep?.uuid !== uuid)
                 .forEach(value => state.steps.set(value[0], value[1]));
             state.steps.delete(uuid)
             return state;
@@ -151,31 +149,7 @@ export const useConnectionsStore = createWithEqualityFn<ConnectionsState>((set)
     },
     setSteps: (steps: Map<string, DslPosition>) => {
         set({steps: steps})
-    },
-    buttons: [],
-    addButton: (button: ButtonPosition) => {
-        set((state: ConnectionsState) => {
-            const index = state.buttons.findIndex(b => b.uuid === button.uuid);
-            if (index !== -1) {
-                state.buttons.splice(index, 1);
-            }
-            state.buttons.push(button);
-            return state;
-        })
-    },
-    clearButtons: () => {
-        set((state: ConnectionsState) => {
-            state.buttons.length = 0;
-            return state;
-        })
-    },
-    deleteButton: (button: ButtonPosition) => {
-        set((state: ConnectionsState) => {
-            const index = state.buttons.findIndex(b => b.uuid === button.uuid);
-            state.buttons.splice(index, 1);
-            return state;
-        })
-    },
+    }
 }), shallow)
 
 type DesignerState = {
diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx
index 5f9467b6..c7651aac 100644
--- a/karavan-web/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx
@@ -14,15 +14,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React, {useEffect} from 'react';
+import React, {JSX, useEffect, useState} from 'react';
 import '../karavan.css';
-import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
-import {ButtonPosition, DslPosition, EventBus} from "../utils/EventBus";
+import {DslPosition, EventBus} from "../utils/EventBus";
 import {CamelUi} from "../utils/CamelUi";
 import {useConnectionsStore, useDesignerStore, useIntegrationStore} from "../DesignerStore";
 import {shallow} from "zustand/shallow";
 import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
 import {TopologyUtils} from "karavan-core/lib/api/TopologyUtils";
+import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
+import {v4 as uuidv4} from "uuid";
 
 const overlapGap: number = 40;
 
@@ -31,34 +32,24 @@ export function DslConnections() {
     const [integration] = useIntegrationStore((state) => [state.integration], shallow)
     const [width, height, top, left, hideLogDSL] = useDesignerStore((s) =>
         [s.width, s.height, s.top, s.left, s.hideLogDSL], shallow)
-    const [steps, addStep, deleteStep, clearSteps, buttons, addButton, clearButtons, deleteButton] =
-        useConnectionsStore((s) => [s.steps, s.addStep, s.deleteStep, s.clearSteps,
-            s.buttons, s.addButton, s.clearButtons, s.deleteButton], shallow)
+    const [steps, addStep, deleteStep, clearSteps] =
+        useConnectionsStore((s) => [s.steps, s.addStep, s.deleteStep, s.clearSteps], shallow)
+
+    const [svgKey, setSvgKey] = useState<string>('svgKey');
 
     useEffect(() => {
         const sub1 = EventBus.onPosition()?.subscribe((evt: DslPosition) => setPosition(evt));
-        const sub2 = EventBus.onButtonPosition()?.subscribe((btn: ButtonPosition) => setButtonPosition(btn));
         return () => {
             sub1?.unsubscribe();
-            sub2?.unsubscribe();
         };
     });
 
     useEffect(() => {
-        const toDelete: string[] = Array.from(steps.keys()).filter(k => CamelDefinitionApiExt.findElementInIntegration(integration, k) === undefined);
-        toDelete.forEach(key => deleteStep(key));
+        const toDelete1: string[] = Array.from(steps.keys()).filter(k => CamelDefinitionApiExt.findElementInIntegration(integration, k) === undefined);
+        toDelete1.forEach(key => deleteStep(key));
+        setSvgKey(uuidv4())
     }, [integration]);
 
-    function setButtonPosition(btn: ButtonPosition) {
-        if (btn.command === "add") {
-            addButton(btn);
-        } else if (btn.command === "delete") {
-            deleteButton(btn);
-        } else if (btn.command === "clean") {
-            clearButtons();
-        }
-    }
-
     function setPosition(evt: DslPosition) {
         if (evt.command === "add") {
             addStep(evt.step.uuid, evt);
@@ -103,8 +94,6 @@ export function DslConnections() {
             return (
                 <g key={pos.step.uuid + "-incoming"}>
                     <circle cx={incomingX} cy={fromY} r={r} className="circle-incoming"/>
-                    {/*<image x={imageX} y={imageY} href={CamelUi.getConnectionIconString(pos.step)} className="icon"/>*/}
-                    {/*<text x={imageX - 5} y={imageY + 40} className="caption" textAnchor="start">{CamelUi.getTitle(pos.step)}</text>*/}
                     <path d={`M ${lineX1},${lineY1} C ${lineX1},${lineY2} ${lineX2},${lineY1}  ${lineX2},${lineY2}`}
                           className="path-incoming" markerEnd="url(#arrowhead)"/>
                 </g>
@@ -189,8 +178,6 @@ export function DslConnections() {
             return (
                 <g key={pos.step.uuid + "-outgoing"}>
                     <circle cx={outgoingX} cy={outgoingY} r={r} className="circle-outgoing"/>
-                    {/*<image x={imageX} y={imageY} href={image} className="icon"/>*/}
-                    {/*<text x={imageX + 25} y={imageY + 40}  className="caption" textAnchor="end">{CamelUi.getOutgoingTitle(pos.step)}</text>*/}
                     <path
                         d={`M ${lineX1},${lineY1} C ${lineXi - 20}, ${lineY1} ${lineX1 - 15},${lineYi} ${lineXi},${lineYi} L ${lineX2},${lineY2}`}
                         className="path-incoming" markerEnd="url(#arrowhead)"/>
@@ -226,79 +213,113 @@ export function DslConnections() {
         )
     }
 
-    function hasSteps(step: CamelElement): boolean {
-        return (step.hasSteps() && !['FromDefinition'].includes(step.dslName))
-            || ['RouteDefinition', 'TryDefinition', 'ChoiceDefinition', 'SwitchDefinition'].includes(step.dslName);
+    function getNext(pos: DslPosition): CamelElement | undefined {
+        if (pos.nextstep) {
+            return pos.nextstep;
+        } else if (pos.parent) {
+            const parent = steps.get(pos.parent.uuid);
+            if (parent) return getNext(parent);
+        }
+    }
+
+    function isSpecial(pos: DslPosition): boolean {
+        return ['ChoiceDefinition', 'MulticastDefinition', 'TryDefinition'].includes(pos.step.dslName);
     }
 
-    function getPreviousStep(pos: DslPosition) {
-        return Array.from(steps.values())
-            .filter(p => pos.parent?.uuid === p.parent?.uuid)
-            .filter(p => p.inSteps)
-            .filter(p => p.position === pos.position - 1)[0];
+    function addArrowToList(list: JSX.Element[], from?: DslPosition, to?: DslPosition, fromHeader?: boolean, toHeader?: boolean): JSX.Element[]  {
+        const result: JSX.Element[] = [...list];
+        if (from && to) {
+            const rect1 = fromHeader === true ? from.headerRect : from.rect;
+            const rect2 = toHeader === true ? to.headerRect : to.rect;
+            const key = from.step.uuid + "->" + to.step.uuid;
+            result.push(getComplexArrow(key, rect1, rect2, toHeader === true));
+        }
+        return result;
     }
 
-    function getArrow(pos: DslPosition) {
-        const endX = pos.headerRect.x + pos.headerRect.width / 2 - left;
-        const endY = pos.headerRect.y - 9 - top;
-        if (pos.parent) {
+    function getArrow(pos: DslPosition): JSX.Element[] {
+        const list: JSX.Element[] = [];
+
+         if (pos.parent && pos.parent.dslName === 'TryDefinition' && pos.position === 0) {
+            const parent = steps.get(pos.parent.uuid);
+            list.push(...addArrowToList(list, parent, pos, true, false))
+        } else if (pos.parent && pos.parent.dslName === 'MulticastDefinition') {
+            const parent = steps.get(pos.parent.uuid);
+            list.push(...addArrowToList(list, parent, pos, true, false))
+            if (parent?.nextstep) {
+                const to = steps.get(parent.nextstep.uuid);
+                list.push(...addArrowToList(list, pos, to, true, true))
+            }
+        } else if (pos.parent && pos.parent.dslName === 'ChoiceDefinition') {
             const parent = steps.get(pos.parent.uuid);
-            const showArrow = pos.prevStep !== undefined && !['TryDefinition', 'ChoiceDefinition'].includes(pos.prevStep.dslName);
-            const name = pos.prevStep?.dslName;
-            if (parent && showArrow) {
-                if ((!pos.inSteps || (pos.inSteps && pos.position === 0)) && parent.step.dslName !== 'MulticastDefinition') {
-                    return getArrows(pos);
-                } else if (parent.step.dslName === 'MulticastDefinition' && pos.inSteps) {
-                    return getArrows(pos)
-                } else if (pos.inSteps && pos.position > 0 && !hasSteps(pos.step)) {
-                    const prev = getPreviousStep(pos);
-                    if (prev) {
-                        const r = hasSteps(prev.step) ? prev.rect : prev.headerRect;
-                        const prevX = r.x + r.width / 2 - left;
-                        const prevY = r.y + r.height - top;
-                        return (
-                            <line name={name} x1={prevX} y1={prevY} x2={endX} y2={endY} className="path"
-                                  key={pos.step.uuid} markerEnd="url(#arrowhead)"/>
-                        )
-                    }
-                } else if (pos.inSteps && pos.position > 0 && hasSteps(pos.step)) {
-                    const prev = getPreviousStep(pos);
-                    if (prev) {
-                        const r = hasSteps(prev.step) ? prev.rect : prev.headerRect;
-                        const prevX = r.x + r.width / 2 - left;
-                        const prevY = r.y + r.height - top;
-                        return (
-                            <line name={name} x1={prevX} y1={prevY} x2={endX} y2={endY} className="path"
-                                  key={pos.step.uuid} markerEnd="url(#arrowhead)"/>
-                        )
-                    }
+            list.push(...addArrowToList(list, parent, pos, true, false))
+        } else if (pos.parent && ['WhenDefinition', 'OtherwiseDefinition', 'CatchDefinition', 'FinallyDefinition'].includes(pos.parent.dslName)) {
+            if (pos.position === 0) {
+                const parent = steps.get(pos.parent.uuid);
+                list.push(...addArrowToList(list, parent, pos, true, false))
+            }
+            if (pos.position === (pos.inStepsLength - 1) && !isSpecial(pos)) {
+                const nextElement = getNext(pos);
+                if (nextElement) {
+                    const next = steps.get(nextElement.uuid);
+                    list.push(...addArrowToList(list, pos, next, true, true))
                 }
             }
+        } else if (pos.step && !isSpecial(pos)) {
+            if (pos.nextstep) {
+                const next = steps.get(pos.nextstep.uuid);
+                const fromHeader = !pos.step.hasSteps();
+                list.push(...addArrowToList(list, pos, next, fromHeader, true))
+            }
+            if (pos.step.hasSteps() && (pos.step as any).steps.length > 0) {
+                const firstStep = (pos.step as any).steps[0];
+                const next = steps.get(firstStep.uuid);
+                list.push(...addArrowToList(list, pos, next, true, true))
+            }
         }
-    }
 
-    function getArrows(pos: DslPosition) {
-        if (pos.parent) {
-            const parent = steps.get(pos?.parent.uuid);
-            if (parent) {
-            const rect1 = parent.headerRect;
-            const rect2 = pos.headerRect;
-            return getComplexArrow(pos.step.uuid, rect1, rect2);
+        if (['WhenDefinition', 'OtherwiseDefinition'].includes(pos.step.dslName) && pos.step.hasSteps() && (pos.step as any).steps.length === 0) {
+            if (pos.nextstep) {
+                const to = steps.get(pos.nextstep.uuid);
+                list.push(...addArrowToList(list, pos, to, true, true))
+            } else {
+                const next = getNext(pos);
+                if (next) {
+                    const to = steps.get(next.uuid);
+                    list.push(...addArrowToList(list, pos, to, true, true))
+                }
             }
         }
-    }
 
-    function getButtonArrow(btn: ButtonPosition) {
-        const rect1 = btn.rect;
-        const uuid = btn.nextstep.uuid;
-        const nextStep = steps.get(uuid);
-        const rect2 = nextStep?.rect;
-        if (rect1 && rect2) {
-            return getComplexArrow(uuid, rect1, rect2);
+        if (pos.parent?.dslName === 'TryDefinition' && pos.inSteps && pos.position === (pos.inStepsLength - 1)) {
+            const parent = steps.get(pos.parent.uuid);
+            if (parent && parent.nextstep) {
+                const to = steps.get(parent.nextstep.uuid);
+                list.push(...addArrowToList(list, pos, to, true, true))
+            }
         }
+
+        if (!isSpecial(pos) && pos.inSteps && pos.nextstep && pos.parent?.dslName !== 'MulticastDefinition') {
+            const next = steps.get(pos.nextstep.uuid);
+            if (pos.step.hasSteps() && pos.prevStep) {
+            } else {
+                list.push(...addArrowToList(list, pos, next, true, true))
+            }
+        }
+
+        if (!isSpecial(pos) && pos.inSteps && pos.nextstep && pos.parent?.dslName !== 'MulticastDefinition') {
+            const next = steps.get(pos.nextstep.uuid);
+            if (next && !isSpecial(next) && next.inSteps) {
+                // console.log(pos)
+                // const to = steps.get(parent.nextstep.uuid);
+                // list.push(...addArrowToList(list, pos, to, true, true))
+            }
+        }
+
+        return list;
     }
 
-    function getComplexArrow(key: string, rect1: DOMRect, rect2: DOMRect) {
+    function getComplexArrow(key: string, rect1: DOMRect, rect2: DOMRect, toHeader: boolean) {
             const startX = rect1.x + rect1.width / 2 - left;
             const startY = rect1.y + rect1.height - top - 2;
             const endX = rect2.x + rect2.width / 2 - left;
@@ -309,7 +330,7 @@ export function DslConnections() {
 
             const radX = gapX > 30 ? 20 : gapX/2;
             const radY = gapY > 30 ? 20 : gapY/2;
-            const endY = rect2.y - top - 9 - radY;
+            const endY = rect2.y - top - radY - (toHeader ? 9 : 6);
 
             const iRadX = startX > endX ? -1 * radX : radX;
             const iRadY = startY > endY ? -1 * radY : radY;
@@ -336,28 +357,27 @@ export function DslConnections() {
                 + ` L ${LX2} ${LY2}`
                 + ` Q ${Q2_X1} ${Q2_Y1} ${Q2_X2} ${Q2_Y2}`
             return (
-                <path key={key} d={path} className="path" markerEnd="url(#arrowhead)"/>
+                <path key={uuidv4()} name={key} d={path} className="path" markerEnd="url(#arrowhead)"/>
             )
     }
 
     function getSvg() {
         const stepsArray = Array.from(steps.values());
+        const arrows = stepsArray.map(pos => getArrow(pos)).flat(1);
+        const uniqueArrows = [...new Map(arrows.map(item =>  [(item as any).key, item])).values()]
         return (
-            <svg
+            <svg key={svgKey}
                 style={{width: width, height: height, position: "absolute", left: 0, top: 0}}
                 viewBox={"0 0 " + (width) + " " + (height)}>
                 <defs>
-                    <marker id="arrowhead" markerWidth="9" markerHeight="6" refX="0" refY="3" orient="auto"
-                            className="arrow">
+                    <marker id="arrowhead" markerWidth="9" markerHeight="6" refX="0" refY="3" orient="auto" className="arrow">
                         <polygon points="0 0, 9 3, 0 6"/>
                     </marker>
                 </defs>
                 {stepsArray.map(pos => getCircle(pos))}
-                {stepsArray.map(pos => getArrow(pos))}
-                {buttons.map(btn => getButtonArrow(btn)).filter(b => b !== undefined)}
+                {uniqueArrows}
                 {getIncomings().map(p => getIncoming(p))}
                 {getOutgoings().map(p => getOutgoing(p))}
-                {/*{getInternals().map((p) => getInternalLines(p)).flat()}*/}
             </svg>
         )
     }
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 b6b1c942..b02ed20b 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
@@ -147,16 +147,17 @@ export function RouteDesigner() {
                      data-click="FLOWS"
                      onClick={event => {unselectElement(event)}}
                      ref={flowRef}>
-                    {routeConfigurations?.map((routeConfiguration, index: number) => (
+                    {routeConfigurations?.map((routeConfiguration, index: number, array) => (
                         <DslElement key={routeConfiguration.uuid}
                                     inSteps={false}
                                     position={index}
                                     step={routeConfiguration}
                                     nextStep={undefined}
                                     prevStep={undefined}
+                                    inStepsLength={array.length}
                                     parent={undefined}/>
                     ))}
-                    {routes?.map((route: any, index: number) => {
+                    {routes?.map((route: any, index: number, array) => {
                         return (
                             <DslElement key={route.uuid}
                                         inSteps={false}
@@ -164,6 +165,7 @@ export function RouteDesigner() {
                                         step={route}
                                         nextStep={undefined}
                                         prevStep={undefined}
+                                        inStepsLength={array.length}
                                         parent={undefined}/>
                         )
                     })}
diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/element/DslElement.css b/karavan-web/karavan-app/src/main/webui/src/designer/route/element/DslElement.css
index 3039f422..61cc5af3 100644
--- a/karavan-web/karavan-app/src/main/webui/src/designer/route/element/DslElement.css
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/element/DslElement.css
@@ -17,12 +17,24 @@
 
 .karavan .dsl-page .flows .step-element .header-route {
     display: block;
-    border: none;
     background: transparent;
-    padding: 0;
-    margin: 3px 24px 10px 24px;
-    /*min-width: 260px;*/
+    border-radius: 42px;
+    padding: 20px;
+    margin: 0;
     z-index: 101;
+    min-width: 260px;
+}
+
+.karavan .dsl-page .flows .step-element .header-bottom-selected {
+    border-bottom: 1px dashed var(--step-border-color-selected);
+}
+
+.karavan .dsl-page .flows .step-element .header-bottom-not-selected {
+    border-bottom: 1px dashed var(--pf-v5-global--Color--200);
+}
+
+.karavan .dsl-page .flows .step-element .header-route:hover {
+    cursor: pointer;
 }
 
 .karavan .step-element .header-route .delete-button {
@@ -41,7 +53,7 @@
 .karavan .step-element .header .delete-button,
 .element-builder .header .delete-button {
     position: absolute;
-    top: -7px;
+    top: -11px;
     line-height: 1;
     border: 0;
     padding: 0;
@@ -65,10 +77,6 @@
     height: 50px;
 }
 
-.karavan .step-element-selected {
-    background-color: rgba(var(--pf-v5-global--palette--blue-50), 1);
-}
-
 .karavan .step-element .selected .header-icon {
     border-color: var(--pf-v5-global--primary-color--100);
     background-color: var(--pf-v5-global--palette--blue-50);
@@ -145,7 +153,7 @@
 
 .karavan .step-element .insert-element-button {
     position: absolute;
-    top: -7px;
+    top: -11px;
     line-height: 1;
     border: 0;
     padding: 0;
@@ -214,4 +222,4 @@
     width: 20px;
     height: 20px;
     background: white;
-}
\ No newline at end of file
+}
diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/element/DslElement.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/route/element/DslElement.tsx
index 1deb46da..967d3321 100644
--- a/karavan-web/karavan-app/src/main/webui/src/designer/route/element/DslElement.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/element/DslElement.tsx
@@ -14,20 +14,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React, {CSSProperties, useMemo, useState} from 'react';
-import {Text, Tooltip,} from '@patternfly/react-core';
+import React, {CSSProperties, useState} from 'react';
+import {Tooltip,} from '@patternfly/react-core';
 import '../../karavan.css';
 import './DslElement.css';
 import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
-import {CamelUi} from "../../utils/CamelUi";
 import {EventBus} from "../../utils/EventBus";
 import {ChildElement, CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
-import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
 import {CamelDisplayUtil} from "karavan-core/lib/api/CamelDisplayUtil";
 import {useDesignerStore, useIntegrationStore} from "../../DesignerStore";
 import {shallow} from "zustand/shallow";
 import {useRouteDesignerHook} from "../useRouteDesignerHook";
-import {AddElementIcon, DeleteElementIcon, InsertElementIcon} from "./DslElementIcons";
+import {AddElementIcon} from "./DslElementIcons";
+import {DslElementHeader} from "./DslElementHeader";
 
 interface Props {
     step: CamelElement,
@@ -36,12 +35,12 @@ interface Props {
     prevStep: CamelElement | undefined,
     inSteps: boolean
     position: number
+    inStepsLength: number
 }
 
 export function DslElement(props: Props) {
 
     const headerRef = React.useRef<HTMLDivElement>(null);
-    const addButtonRef = React.useRef<HTMLDivElement>(null);
     const {
         selectElement,
         moveElement,
@@ -70,11 +69,6 @@ export function DslElement(props: Props) {
         }
     }
 
-    function onDeleteElement(evt: React.MouseEvent) {
-        evt.stopPropagation();
-        onShowDeleteConfirmation(props.step.uuid);
-    }
-
     function onSelectElement(evt: React.MouseEvent) {
         evt.stopPropagation();
         selectElement(props.step);
@@ -100,18 +94,15 @@ export function DslElement(props: Props) {
         return selectedUuids.includes(props.step.uuid);
     }
 
-    function isElementHidden(): boolean {
-        return props.step.dslName === 'LogDefinition' && hideLogDSL;
-    }
-
     function hasBorder(): boolean {
         const step = props.step;
-        if (['FilterDefinition'].includes(step.dslName)) {
+        if (['FilterDefinition', 'RouteDefinition', 'RouteConfigurationDefinition'].includes(step.dslName)) {
             return true;
         }
-        if (['FromDefinition',
-            'RouteDefinition',
+        if ([
+            'FromDefinition',
             'TryDefinition',
+            'MulticastDefinition',
             'CatchDefinition', 'FinallyDefinition',
             'ChoiceDefinition',
             'SwitchDefinition', 'WhenDefinition', 'OtherwiseDefinition'
@@ -125,11 +116,6 @@ export function DslElement(props: Props) {
         return ['FromDefinition', 'RouteConfigurationDefinition', 'RouteDefinition', 'WhenDefinition', 'OtherwiseDefinition'].includes(props.step.dslName);
     }
 
-    function isWide(): boolean {
-        return ['RouteConfigurationDefinition', 'RouteDefinition', 'ChoiceDefinition', 'SwitchDefinition', 'MulticastDefinition', 'TryDefinition', 'CircuitBreakerDefinition']
-            .includes(props.step.dslName);
-    }
-
     function isAddStepButtonLeft(): boolean {
         return ['MulticastDefinition']
             .includes(props.step.dslName);
@@ -139,9 +125,6 @@ export function DslElement(props: Props) {
         return ['MulticastDefinition'].includes(props.step.dslName);
     }
 
-    function isRoot(): boolean {
-        return ['RouteConfigurationDefinition', 'RouteDefinition'].includes(props.step?.dslName);
-    }
 
     function isInStepWithChildren() {
         const step: CamelElement = props.step;
@@ -149,71 +132,10 @@ export function DslElement(props: Props) {
         return children.filter((c: ChildElement) => c.name === 'steps' || c.multiple).length > 0 && props.inSteps;
     }
 
-    function getChildrenInfo(step: CamelElement): [boolean, number, boolean, number, number] {
-        const children = CamelDefinitionApiExt.getElementChildrenDefinition(step.dslName);
-        const hasStepsField = children.filter((c: ChildElement) => c.name === 'steps').length === 1;
-        const stepsChildrenCount = children
-            .filter(c => c.name === 'steps')
-            .map((child: ChildElement, index: number) => {
-                const children: CamelElement[] = CamelDefinitionApiExt.getElementChildren(step, child);
-                return children.length;
-            }).reduce((a, b) => a + b, 0);
-
-        const hasNonStepsFields = children.filter(c => c.name !== 'steps' && c.name !== 'expression' && c.name !== 'onWhen').length > 0;
-        const childrenCount = children
-            .map((child: ChildElement, index: number) => {
-                const children: CamelElement[] = CamelDefinitionApiExt.getElementChildren(step, child);
-                return children.length;
-            }).reduce((a, b) => a + b, 0);
-        const nonStepChildrenCount = childrenCount - stepsChildrenCount;
-        return [hasStepsField, stepsChildrenCount, hasNonStepsFields, nonStepChildrenCount, childrenCount]
-    }
-
-    function hasWideChildrenElement() {
-        const [hasStepsField, stepsChildrenCount, hasNonStepsFields, nonStepChildrenCount, childrenCount] = getChildrenInfo(props.step);
-        if (isHorizontal() && stepsChildrenCount > 1) return true;
-        else if (hasStepsField && stepsChildrenCount > 0 && hasNonStepsFields && nonStepChildrenCount > 0) return true;
-        else if (!hasStepsField && hasNonStepsFields && childrenCount > 1) return true;
-        else if (hasStepsField && stepsChildrenCount > 0 && hasNonStepsFields && childrenCount > 1) return true;
-        else return false;
-    }
-
-    function hasBorderOverSteps(step: CamelElement) {
-        const [hasStepsField, stepsChildrenCount, hasNonStepsFields, nonStepChildrenCount] = getChildrenInfo(step);
-        if (hasStepsField && stepsChildrenCount > 0 && hasNonStepsFields && nonStepChildrenCount > 0) return true;
-        else return false;
-    }
-
-    function getHeaderStyle() {
-        const style: CSSProperties = {
-            width: isWide() ? "100%" : "",
-            fontWeight: isElementSelected() ? "bold" : "normal",
-        };
-        return style;
-    }
-
-    function sendButtonPosition(el: HTMLButtonElement | null) {
-        const {nextStep, step, parent} = props;
-        let needArrow = !hasBorder() && !['ChoiceDefinition', 'MulticastDefinition', 'TryDefinition'].includes(step.dslName);
-
-        if (parent
-            && ['TryDefinition'].includes(parent.dslName)
-            && !['CatchDefinition', 'FinallyDefinition'].includes(step.dslName)) {
-            needArrow = true;
-        }
-
-        if (el && nextStep && needArrow) {
-            const rect = headerRef.current?.getBoundingClientRect();
-
-            if (rect)
-                EventBus.sendButtonPosition("add", step.uuid, nextStep, rect);
-        }
-    }
 
     function sendPosition(el: HTMLDivElement | null) {
-        const {step, prevStep, parent} = props;
+        const {step, prevStep, nextStep, parent, inSteps, inStepsLength} = props;
         const isSelected = isElementSelected();
-        const isHidden = isElementHidden();
         if (el) {
             const header = Array.from(el.childNodes.values()).filter((n: any) => n.classList.contains("header"))[0];
             if (header) {
@@ -221,109 +143,14 @@ export function DslElement(props: Props) {
                 const headerRect = headerIcon.getBoundingClientRect();
                 const rect = el.getBoundingClientRect();
                 if (step.showChildren) {
-                    if (isHidden) {
-                        EventBus.sendPosition("add", step, prevStep, parent, rect, headerRect, props.position, props.inSteps, isSelected);
-                    } else {
-                        EventBus.sendPosition("add", step, prevStep, parent, rect, headerRect, props.position, props.inSteps, isSelected);
-                    }
-                } else {
-                    EventBus.sendPosition("delete", step, prevStep, parent, new DOMRect(), new DOMRect(), 0);
+                    EventBus.sendPosition("add", step, prevStep, nextStep, parent, rect, headerRect, props.position, inStepsLength, inSteps, isSelected);
                 }
             }
-        }
-    }
-
-    function getAvailableModels() { // TODO: make static list-of-values instead
-        const step: CamelElement = props.step
-        return CamelUi.getSelectorModelsForParent(step.dslName, false);
-    }
-
-    const availableModels = useMemo(
-        () => getAvailableModels(),
-        [props.step.dslName]
-    );
-
-
-    function getHeader() {
-        const step: CamelElement = props.step;
-        const parent = props.parent;
-        const inRouteConfiguration = parent !== undefined && parent.dslName === 'RouteConfigurationDefinition';
-        const showAddButton = !['CatchDefinition', 'RouteDefinition'].includes(step.dslName) && availableModels.length > 0;
-        const showInsertButton =
-            !['FromDefinition', 'RouteConfigurationDefinition', 'RouteDefinition', 'CatchDefinition', 'FinallyDefinition', 'WhenDefinition', 'OtherwiseDefinition'].includes(step.dslName)
-            && !inRouteConfiguration;
-        const headerClass = ['RouteConfigurationDefinition', 'RouteDefinition'].includes(step.dslName) ? "header-route" : "header"
-        const headerClasses = isElementSelected() ? headerClass + " selected" : headerClass;
-        return (
-            <div className={"dsl-element " + headerClasses} style={getHeaderStyle()} ref={headerRef}>
-                {!['RouteConfigurationDefinition', 'RouteDefinition'].includes(props.step.dslName) &&
-                    <div
-                        ref={el => sendPosition(el)}
-                        className={"header-icon"}
-                        style={isWide() ? {width: ""} : {}}>
-                        {CamelUi.getIconForElement(step)}
-                    </div>
-                }
-                <div className={hasWideChildrenElement() ? "header-text" : ""}>
-                    {hasWideChildrenElement() && <div className="spacer"/>}
-                    {getHeaderTextWithTooltip(step)}
-                </div>
-                {showInsertButton && getInsertElementButton()}
-                {getDeleteButton()}
-                {showAddButton && getAddElementButton()}
-            </div>
-        )
-    }
-
-    function getHeaderText(step: CamelElement): string {
-        if (isKamelet() && step.dslName === 'ToDefinition' && (step as any).uri === 'kamelet:sink') {
-            return "Sink";
-        } else if (isKamelet() && step.dslName === 'FromDefinition' && (step as any).uri === 'kamelet:source') {
-            return "Source";
         } else {
-            return (step as any).description ? (step as any).description : CamelUi.getElementTitle(props.step);
+            EventBus.sendPosition("delete", step, prevStep, nextStep, parent, new DOMRect(), new DOMRect(), 0, 0);
         }
     }
 
-    function getHeaderTextWithTooltip(step: CamelElement) {
-        const checkRequired = CamelUtil.checkRequired(step);
-        const title = getHeaderText(step);
-        let className = hasWideChildrenElement() ? "text text-right" : "text text-bottom";
-        if (!checkRequired[0]) className = className + " header-text-required";
-        if (checkRequired[0]) {
-            return <Text className={className}>{title}</Text>
-        } else return (
-            <Tooltip position={"right"} className="tooltip-required-field"
-                     content={checkRequired[1].map((text, i) => (<div key={i}>{text}</div>))}>
-                <Text className={className}>{title}</Text>
-            </Tooltip>
-        )
-    }
-
-    function getHeaderWithTooltip(tooltip: string | undefined) {
-        return (
-            <>
-                {getHeader()}
-                <Tooltip triggerRef={headerRef} position={"left"} content={<div>{tooltip}</div>}/>
-            </>
-
-        )
-    }
-
-    function getHeaderTooltip(): string | undefined {
-        if (CamelUi.isShowExpressionTooltip(props.step)) return CamelUi.getExpressionTooltip(props.step);
-        if (CamelUi.isShowUriTooltip(props.step)) return CamelUi.getUriTooltip(props.step);
-        return undefined;
-    }
-
-    function getElementHeader() {
-        const tooltip = getHeaderTooltip();
-        if (tooltip !== undefined && !isDragging) {
-            return getHeaderWithTooltip(tooltip);
-        }
-        return getHeader();
-    }
-
     function getChildrenStyle() {
         const style: CSSProperties = {
             display: "flex",
@@ -333,10 +160,7 @@ export function DslElement(props: Props) {
     }
 
     function getChildrenElementsStyle(child: ChildElement, notOnlySteps: boolean) {
-        const step = props.step;
-        const isBorder = child.name === 'steps' && hasBorderOverSteps(step);
         const style: CSSProperties = {
-            // borderStyle: isBorder ? "dotted" : "none",
             borderColor: "var(--step-border-color)",
             borderWidth: "1px",
             borderRadius: "16px",
@@ -375,10 +199,12 @@ export function DslElement(props: Props) {
             return (
                 <div className={child.name + " has-child"} style={getChildrenElementsStyle(child, notOnlySteps)}
                      key={step.uuid + "-child-" + index}>
-                    {children.map((element, index) => {
+                    {children.map((element, index, array) => {
                             let prevStep = children.at(index - 1);
-                            let nextStep = undefined;
-                            if (['TryDefinition', 'ChoiceDefinition'].includes(step.dslName)) {
+                            let nextStep: CamelElement | undefined = undefined;
+                            if ('ChoiceDefinition' === step.dslName) {
+                                nextStep = props.nextStep;
+                            } else if ('TryDefinition' === step.dslName && ['CatchDefinition', 'FinallyDefinition'].includes(element.dslName)) {
                                 nextStep = props.nextStep;
                             } else {
                                 nextStep = children.at(index + 1);
@@ -390,6 +216,7 @@ export function DslElement(props: Props) {
                                     step={element}
                                     nextStep={nextStep}
                                     prevStep={prevStep}
+                                    inStepsLength={array.length}
                                     parent={step}/>
                             </div>)
                         }
@@ -408,16 +235,14 @@ export function DslElement(props: Props) {
     }
 
     function getAddStepButton() {
-        const {step, nextStep} = props;
+        const {step} = props;
         const hideAddButton = step.dslName === 'StepDefinition' && !CamelDisplayUtil.isStepDefinitionExpanded(integration, step.uuid, selectedUuids.at(0));
         if (hideAddButton) return (<></>)
         else return (
-            <div ref={addButtonRef}>
-                <Tooltip position={"bottom"}
+                <Tooltip position={"left"}
                          content={<div>{"Add step to " + CamelDisplayUtil.getTitle(step)}</div>}
                 >
                     <button type="button"
-                            ref={el => sendButtonPosition(el)}
                             aria-label="Add"
                             onClick={e => onOpenSelector(e)}
                             className={isAddStepButtonLeft() ? "add-button add-button-left" : "add-button add-button-bottom"}>
@@ -425,51 +250,12 @@ export function DslElement(props: Props) {
                     </button>
 
                 </Tooltip>
-            </div>
-        )
-    }
-
-    function getAddElementButton() {
-        return (
-            <Tooltip position={"bottom"}
-                     content={<div>{"Add DSL element to " + CamelDisplayUtil.getTitle(props.step)}</div>}>
-                <button
-                    type="button"
-                    aria-label="Add"
-                    onClick={e => onOpenSelector(e, false)}
-                    className={"add-element-button"}>
-                    <AddElementIcon/>
-                </button>
-            </Tooltip>
-        )
-    }
-
-    function getInsertElementButton() {
-        return (
-            <Tooltip position={"left"} content={<div>{"Insert element before"}</div>}>
-                <button type="button"
-                        aria-label="Insert"
-                        onClick={e => onOpenSelector(e, true, true)}
-                        className={"insert-element-button"}>
-                    <InsertElementIcon/>
-                </button>
-            </Tooltip>
-        )
-    }
-
-    function getDeleteButton() {
-        return (
-            <Tooltip position={"right"} content={<div>{"Delete element"}</div>}>
-                <button type="button" aria-label="Delete" onClick={e => onDeleteElement(e)} className="delete-button">
-                    <DeleteElementIcon/>
-                </button>
-            </Tooltip>
         )
     }
 
     const element: CamelElement = props.step;
     const className = "step-element"
-        + (isElementSelected() ? " step-element-selected" : "") + (!props.step.showChildren ? " hidden-step" : "")
+        + (!props.step.showChildren ? " hidden-step" : "")
         + ((element as any).disabled ? " disabled " : "");
     return (
         <div key={"root" + element.uuid}
@@ -516,7 +302,14 @@ export function DslElement(props: Props) {
              onDrop={event => dragElement(event, element)}
              draggable={!isNotDraggable()}
         >
-            {getElementHeader()}
+            <DslElementHeader headerRef={headerRef}
+                              step={props.step}
+                              parent={props.parent}
+                              nextStep={props.nextStep}
+                              prevStep={props.prevStep}
+                              inSteps={props.inSteps}
+                              isDragging={isDragging}
+                              position={props.position}/>
             {getChildElements()}
         </div>
     )
diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/element/DslElementHeader.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/route/element/DslElementHeader.tsx
new file mode 100644
index 00000000..d201d105
--- /dev/null
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/element/DslElementHeader.tsx
@@ -0,0 +1,275 @@
+/*
+ * 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, {CSSProperties, useMemo} from 'react';
+import {Text, Tooltip,} from '@patternfly/react-core';
+import '../../karavan.css';
+import './DslElement.css';
+import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
+import {CamelUi} from "../../utils/CamelUi";
+import {ChildElement, CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
+import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
+import {CamelDisplayUtil} from "karavan-core/lib/api/CamelDisplayUtil";
+import {useDesignerStore} from "../../DesignerStore";
+import {shallow} from "zustand/shallow";
+import {useRouteDesignerHook} from "../useRouteDesignerHook";
+import {AddElementIcon, DeleteElementIcon, InsertElementIcon} from "./DslElementIcons";
+import { RouteConfigurationDefinition} from "karavan-core/lib/model/CamelDefinition";
+
+interface Props {
+    headerRef: React.RefObject<HTMLDivElement>
+    step: CamelElement,
+    parent: CamelElement | undefined,
+    nextStep: CamelElement | undefined,
+    prevStep: CamelElement | undefined,
+    inSteps: boolean
+    position: number
+    isDragging: boolean
+}
+
+export function DslElementHeader(props: Props) {
+
+    const {
+        selectElement,
+        moveElement,
+        onShowDeleteConfirmation,
+        openSelector,
+        isKamelet,
+        isSourceKamelet,
+        isActionKamelet
+    } = useRouteDesignerHook();
+
+    const [selectedUuids, selectedStep, showMoveConfirmation, setShowMoveConfirmation, hideLogDSL, setMoveElements] =
+        useDesignerStore((s) =>
+            [s.selectedUuids, s.selectedStep, s.showMoveConfirmation, s.setShowMoveConfirmation, s.hideLogDSL, s.setMoveElements], shallow)
+
+    function onOpenSelector(evt: React.MouseEvent, showSteps: boolean = true, isInsert: boolean = false) {
+        evt.stopPropagation();
+        if (isInsert && props.parent) {
+            openSelector(props.parent.uuid, props.parent.dslName, showSteps, props.position);
+        } else {
+            openSelector(props.step.uuid, props.step.dslName, showSteps);
+        }
+    }
+
+    function onDeleteElement(evt: React.MouseEvent) {
+        evt.stopPropagation();
+        onShowDeleteConfirmation(props.step.uuid);
+    }
+
+    function isElementSelected(): boolean {
+        return selectedUuids.includes(props.step.uuid);
+    }
+
+    function isWide(): boolean {
+        return ['RouteConfigurationDefinition', 'RouteDefinition', 'ChoiceDefinition', 'MulticastDefinition', 'TryDefinition', 'CircuitBreakerDefinition']
+            .includes(props.step.dslName);
+    }
+
+    function isHorizontal(): boolean {
+        return ['MulticastDefinition'].includes(props.step.dslName);
+    }
+
+    function getChildrenInfo(step: CamelElement): [boolean, number, boolean, number, number] {
+        const children = CamelDefinitionApiExt.getElementChildrenDefinition(step.dslName);
+        const hasStepsField = children.filter((c: ChildElement) => c.name === 'steps').length === 1;
+        const stepsChildrenCount = children
+            .filter(c => c.name === 'steps')
+            .map((child: ChildElement, index: number) => {
+                const children: CamelElement[] = CamelDefinitionApiExt.getElementChildren(step, child);
+                return children.length;
+            }).reduce((a, b) => a + b, 0);
+
+        const hasNonStepsFields = children.filter(c => c.name !== 'steps' && c.name !== 'expression' && c.name !== 'onWhen').length > 0;
+        const childrenCount = children
+            .map((child: ChildElement, index: number) => {
+                const children: CamelElement[] = CamelDefinitionApiExt.getElementChildren(step, child);
+                return children.length;
+            }).reduce((a, b) => a + b, 0);
+        const nonStepChildrenCount = childrenCount - stepsChildrenCount;
+        return [hasStepsField, stepsChildrenCount, hasNonStepsFields, nonStepChildrenCount, childrenCount]
+    }
+
+    function hasWideChildrenElement() {
+        const [hasStepsField, stepsChildrenCount, hasNonStepsFields, nonStepChildrenCount, childrenCount] = getChildrenInfo(props.step);
+        if (isHorizontal() && stepsChildrenCount > 1) return true;
+        else if (hasStepsField && stepsChildrenCount > 0 && hasNonStepsFields && nonStepChildrenCount > 0) return true;
+        else if (!hasStepsField && hasNonStepsFields && childrenCount > 1) return true;
+        else if (hasStepsField && stepsChildrenCount > 0 && hasNonStepsFields && childrenCount > 1) return true;
+        else return false;
+    }
+
+    function getHeaderStyle() {
+        const style: CSSProperties = {
+            width: isWide() ? "100%" : "",
+            fontWeight: isElementSelected() ? "bold" : "normal",
+        };
+        return style;
+    }
+
+    function getAvailableModels() { // TODO: make static list-of-values instead
+        const step: CamelElement = props.step
+        return CamelUi.getSelectorModelsForParent(step.dslName, false);
+    }
+
+    const availableModels = useMemo(
+        () => getAvailableModels(),
+        [props.step.dslName]
+    );
+
+    function hasElements(rc: RouteConfigurationDefinition): boolean {
+        return (rc.interceptFrom !== undefined && rc.interceptFrom.length > 0)
+    || (rc.intercept !== undefined && rc.intercept.length > 0)
+    || (rc.interceptSendToEndpoint !== undefined && rc.interceptSendToEndpoint.length > 0)
+    || (rc.onException !== undefined && rc.onException.length > 0)
+    || (rc.onCompletion !== undefined && rc.onCompletion.length > 0)
+    }
+
+    function getHeaderClasses(): string {
+        const classes: string[] = [];
+        const step: CamelElement = props.step;
+        if (step.dslName === 'RouteDefinition') {
+            classes.push('header-route')
+            classes.push('header-bottom-line')
+            classes.push(isElementSelected() ? 'header-bottom-selected' : 'header-bottom-not-selected')
+        } else if (step.dslName === 'RouteConfigurationDefinition') {
+            classes.push('header-route')
+            if (hasElements(step)) classes.push('header-bottom-line')
+            classes.push(isElementSelected() ? 'header-bottom-selected' : 'header-bottom-not-selected')
+        } else {
+            classes.push('header')
+        }
+        if (isElementSelected()) {
+            classes.push("selected")
+        }
+        return classes.join(" ");
+    }
+
+    function getHeader() {
+        const step: CamelElement = props.step;
+        const parent = props.parent;
+        const inRouteConfiguration = parent !== undefined && parent.dslName === 'RouteConfigurationDefinition';
+        const showAddButton = !['CatchDefinition', 'RouteDefinition'].includes(step.dslName) && availableModels.length > 0;
+        const showInsertButton =
+            !['FromDefinition', 'RouteConfigurationDefinition', 'RouteDefinition', 'CatchDefinition', 'FinallyDefinition', 'WhenDefinition', 'OtherwiseDefinition'].includes(step.dslName)
+            && !inRouteConfiguration;
+        const headerClasses = getHeaderClasses();
+        return (
+            <div className={"dsl-element " + headerClasses} style={getHeaderStyle()} ref={props.headerRef}>
+                {!['RouteConfigurationDefinition', 'RouteDefinition'].includes(props.step.dslName) &&
+                    <div
+                        className={"header-icon"}
+                        style={isWide() ? {width: ""} : {}}>
+                        {CamelUi.getIconForElement(step)}
+                    </div>
+                }
+                <div className={hasWideChildrenElement() ? "header-text" : ""}>
+                    {hasWideChildrenElement() && <div className="spacer"/>}
+                    {getHeaderTextWithTooltip(step)}
+                </div>
+                {showInsertButton && getInsertElementButton()}
+                {getDeleteButton()}
+                {showAddButton && getAddElementButton()}
+            </div>
+        )
+    }
+
+    function getHeaderText(step: CamelElement): string {
+        if (isKamelet() && step.dslName === 'ToDefinition' && (step as any).uri === 'kamelet:sink') {
+            return "Sink";
+        } else if (isKamelet() && step.dslName === 'FromDefinition' && (step as any).uri === 'kamelet:source') {
+            return "Source";
+        } else {
+            return (step as any).description ? (step as any).description : CamelUi.getElementTitle(props.step);
+        }
+    }
+
+    function getHeaderTextWithTooltip(step: CamelElement) {
+        const title = getHeaderText(step);
+        const checkRequired = CamelUtil.checkRequired(step);
+        let className = hasWideChildrenElement() ? "text text-right" : "text text-bottom";
+        if (!checkRequired[0]) className = className + " header-text-required";
+        if (checkRequired[0]) {
+            return <Text className={className}>{title}</Text>
+        } else return (
+            <Tooltip position={"right"} className="tooltip-required-field"
+                     content={checkRequired[1].map((text, i) => (<div key={i}>{text}</div>))}>
+                <Text className={className}>{title}</Text>
+            </Tooltip>
+        )
+    }
+
+    function getHeaderWithTooltip(tooltip: string | undefined) {
+        return (
+            <>
+                {getHeader()}
+                <Tooltip triggerRef={props.headerRef} position={"left"} content={<div>{tooltip}</div>}/>
+            </>
+
+        )
+    }
+
+    function getHeaderTooltip(): string | undefined {
+        if (CamelUi.isShowExpressionTooltip(props.step)) return CamelUi.getExpressionTooltip(props.step);
+        if (CamelUi.isShowUriTooltip(props.step)) return CamelUi.getUriTooltip(props.step);
+        return undefined;
+    }
+
+
+    function getAddElementButton() {
+        return (
+            <Tooltip position={"bottom"}
+                     content={<div>{"Add DSL element to " + CamelDisplayUtil.getTitle(props.step)}</div>}>
+                <button
+                    type="button"
+                    aria-label="Add"
+                    onClick={e => onOpenSelector(e, false)}
+                    className={"add-element-button"}>
+                    <AddElementIcon/>
+                </button>
+            </Tooltip>
+        )
+    }
+
+    function getInsertElementButton() {
+        return (
+            <Tooltip position={"left"} content={<div>{"Insert element before"}</div>}>
+                <button type="button"
+                        aria-label="Insert"
+                        onClick={e => onOpenSelector(e, true, true)}
+                        className={"insert-element-button"}>
+                    <InsertElementIcon/>
+                </button>
+            </Tooltip>
+        )
+    }
+
+    function getDeleteButton() {
+        return (
+            <Tooltip position={"right"} content={<div>{"Delete element"}</div>}>
+                <button type="button" aria-label="Delete" onClick={e => onDeleteElement(e)} className="delete-button">
+                    <DeleteElementIcon/>
+                </button>
+            </Tooltip>
+        )
+    }
+
+    const tooltip = getHeaderTooltip();
+    if (tooltip !== undefined && !props.isDragging) {
+        return getHeaderWithTooltip(tooltip);
+    }
+    return getHeader();
+}
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 b65966a0..1457adba 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
@@ -105,8 +105,7 @@ export function useRouteDesignerHook () {
     }
 
     const deleteElement = () =>  {
-        EventBus.sendPosition("clean", new CamelElement(""), undefined, undefined, new DOMRect(), new DOMRect(), 0);
-        EventBus.sendButtonPosition("clean", '',  new CamelElement(""), new DOMRect());
+        EventBus.sendPosition("clean", new CamelElement(""), undefined,undefined, undefined, new DOMRect(), new DOMRect(), 0, 0);
         let i = integration;
         selectedUuids.forEach(uuidToDelete => {
              i = CamelDefinitionApiExt.deleteStepFromIntegration(i, uuidToDelete);
diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx
index 61a7d27c..5caae0a7 100644
--- a/karavan-web/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx
@@ -337,7 +337,7 @@ export class CamelUi {
     static getElementTitle = (element: CamelElement): string => {
         if (element.dslName === 'RouteDefinition') {
             const routeId = (element as RouteDefinition).id
-            return routeId ? "Route: " + routeId : CamelUtil.capitalizeName((element as any).stepName);
+            return routeId ? routeId : CamelUtil.capitalizeName((element as any).stepName);
         } else if (['ToDefinition', 'ToDynamicDefinition', 'FromDefinition', 'KameletDefinition'].includes(element.dslName) && (element as any).uri) {
             const uri = (element as any).uri;
             const kameletTitle = uri && uri.startsWith("kamelet:") ? KameletApi.findKameletByUri(uri)?.title() : undefined;
@@ -787,4 +787,5 @@ export class CamelUi {
             .forEach((f: any) => result.push(f));
         return result;
     }
+
 }
\ No newline at end of file
diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/utils/EventBus.ts b/karavan-web/karavan-app/src/main/webui/src/designer/utils/EventBus.ts
index 55906f0f..687bec9d 100644
--- a/karavan-web/karavan-app/src/main/webui/src/designer/utils/EventBus.ts
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/utils/EventBus.ts
@@ -18,30 +18,15 @@ import {Subject} from 'rxjs';
 import {CamelElement, Integration} from "karavan-core/lib/model/IntegrationDefinition";
 import {v4 as uuidv4} from "uuid";
 
-export class ButtonPosition {
-    uuid: string = '';
-    nextstep: CamelElement = new CamelElement("");
-    rect: DOMRect = new DOMRect();
-    command: "add" | "delete" | "clean" = "add";
-
-    constructor(command: "add" | "delete" | "clean",
-                uuid: string,
-                nextstep: CamelElement,
-                rect: DOMRect) {
-        this.uuid = uuid;
-        this.command = command;
-        this.nextstep = nextstep;
-        this.rect = rect;
-    }
-}
-
 export class DslPosition {
     step: CamelElement = new CamelElement("");
     prevStep: CamelElement | undefined;
+    nextstep: CamelElement | undefined;
     parent: CamelElement | undefined;
     inSteps: boolean = false;
     isSelected: boolean = false;
     position: number = 0;
+    inStepsLength: number = 0;
     rect: DOMRect = new DOMRect();
     headerRect: DOMRect = new DOMRect();
     command: "add" | "delete" | "clean" = "add";
@@ -49,20 +34,24 @@ export class DslPosition {
     constructor(command: "add" | "delete" | "clean",
                 step: CamelElement,
                 prevStep: CamelElement | undefined,
+                nextstep: CamelElement | undefined,
                 parent:CamelElement | undefined,
                 rect: DOMRect,
                 headerRect:DOMRect,
                 position: number,
+                inStepsLength: number,
                 inSteps: boolean = false,
                 isSelected: boolean = false) {
         this.command = command;
         this.step = step;
+        this.nextstep = nextstep;
         this.prevStep = prevStep;
         this.parent = parent;
         this.rect = rect;
         this.headerRect = headerRect;
         this.inSteps = inSteps;
         this.position = position;
+        this.inStepsLength = inStepsLength;
         this.isSelected = isSelected;
     }
 }
@@ -104,25 +93,22 @@ export class ToastMessage {
     }
 }
 const dslPositions = new Subject<DslPosition>();
-const buttonPositions = new Subject<ButtonPosition>();
 
 export const EventBus = {
     sendPosition: (command: "add" | "delete" | "clean",
                    step: CamelElement,
                    prevStep: CamelElement | undefined,
+                   nextstep: CamelElement | undefined,
                    parent: CamelElement | undefined,
                    rect: DOMRect,
                    headerRect: DOMRect,
                    position: number,
+                   inStepsLength: number,
                    inSteps: boolean = false,
-                   isSelected: boolean = false) => dslPositions.next(new DslPosition(command, step, prevStep, parent, rect, headerRect, position, inSteps, isSelected)),
+                   isSelected: boolean = false) => dslPositions.next(
+                       new DslPosition(command, step, prevStep, nextstep, parent, rect, headerRect, position, inStepsLength, inSteps, isSelected)),
     onPosition: () => dslPositions.asObservable(),
 
-    sendButtonPosition: (command: "add" | "delete" | "clean", uuid: string,
-                   nextStep: CamelElement,
-                   rect: DOMRect) => buttonPositions.next(new ButtonPosition(command, uuid, nextStep, rect)),
-    onButtonPosition: () => buttonPositions.asObservable(),
-
     sendIntegrationUpdate: (i: Integration, propertyOnly: boolean) => updates.next(new IntegrationUpdate(i, propertyOnly)),
     onIntegrationUpdate: () => updates.asObservable(),
 


(camel-karavan) 02/04: Preview fixes of new Designer

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 9d503ba51812e6b5e702172fd08b355005326749
Author: Marat Gubaidullin <ma...@talismancloud.io>
AuthorDate: Wed Dec 6 20:35:19 2023 -0500

    Preview fixes of new Designer
---
 karavan-designer/public/example/demo.camel.yaml    |  26 +++-
 karavan-designer/src/designer/DesignerStore.ts     |  31 ++--
 .../src/designer/route/DslConnections.tsx          | 170 ++++++++++++---------
 .../src/designer/route/RouteDesigner.tsx           |   6 +-
 .../src/designer/route/element/DslElement.css      |  19 +--
 .../src/designer/route/element/DslElement.tsx      |  55 ++-----
 .../designer/route/element/DslElementHeader.tsx    |  42 +----
 .../src/designer/route/useRouteDesignerHook.tsx    |   2 +-
 karavan-designer/src/designer/utils/EventBus.ts    |  11 +-
 9 files changed, 175 insertions(+), 187 deletions(-)

diff --git a/karavan-designer/public/example/demo.camel.yaml b/karavan-designer/public/example/demo.camel.yaml
index ccd98241..1bc2b8ae 100644
--- a/karavan-designer/public/example/demo.camel.yaml
+++ b/karavan-designer/public/example/demo.camel.yaml
@@ -25,6 +25,15 @@
                   - to:
                       uri: amqp
                       id: to-fbfe
+                  - choice:
+                      when:
+                        - expression:
+                            simple:
+                              id: simple-e78b
+                          id: when-b7d0
+                      otherwise:
+                        id: otherwise-40d0
+                      id: choice-8f6b
             otherwise:
               id: otherwise-382c
               steps:
@@ -38,12 +47,6 @@
               - to:
                   uri: kamelet:azure-cosmosdb-sink
                   id: to-1394
-- route:
-    nodePrefixId: route-d10
-    id: route-3ad9
-    from:
-      uri: kamelet:azure-storage-datalake-source
-      id: from-1516
 - route:
     nodePrefixId: route-171
     id: route-99f9
@@ -85,6 +88,15 @@
                   - log:
                       message: ${body}
                       id: log-77df
+                  - choice:
+                      when:
+                        - expression:
+                            simple:
+                              id: simple-c7db
+                          id: when-f058
+                      otherwise:
+                        id: otherwise-1e11
+                      id: choice-8374
                   - wireTap:
                       id: wireTap-a25e
             doFinally:
@@ -113,5 +125,7 @@
                           simple:
                             id: simple-f0dc
                         id: setBody-3c0c
+              - process:
+                  id: process-6d06
         - circuitBreaker:
             id: circuitBreaker-4af8
diff --git a/karavan-designer/src/designer/DesignerStore.ts b/karavan-designer/src/designer/DesignerStore.ts
index f9bc38d1..815f0f68 100644
--- a/karavan-designer/src/designer/DesignerStore.ts
+++ b/karavan-designer/src/designer/DesignerStore.ts
@@ -120,9 +120,9 @@ interface ConnectionsState {
     deleteStep: (uuid: string) => void;
     clearSteps: () => void;
     setSteps: (steps: Map<string, DslPosition>) => void;
-    buttons: ButtonPosition[];
-    addButton: (button: ButtonPosition) => void;
-    deleteButton: (button: ButtonPosition) => void;
+    buttons: Map<string, ButtonPosition>;
+    addButton: (uuid: string, button: ButtonPosition) => void;
+    deleteButton: (uuid: string) => void;
     clearButtons: () => void;
 }
 
@@ -152,27 +152,24 @@ export const useConnectionsStore = createWithEqualityFn<ConnectionsState>((set)
     setSteps: (steps: Map<string, DslPosition>) => {
         set({steps: steps})
     },
-    buttons: [],
-    addButton: (button: ButtonPosition) => {
-        set((state: ConnectionsState) => {
-            const index = state.buttons.findIndex(b => b.uuid === button.uuid);
-            if (index !== -1) {
-                state.buttons.splice(index, 1);
-            }
-            state.buttons.push(button);
-            return state;
-        })
+    buttons: new Map<string, ButtonPosition>(),
+    addButton: (uuid: string, button: ButtonPosition) => {
+        set(state => ({
+            buttons: new Map(state.buttons).set(uuid, button),
+        }))
     },
     clearButtons: () => {
         set((state: ConnectionsState) => {
-            state.buttons.length = 0;
+            state.buttons.clear();
             return state;
         })
     },
-    deleteButton: (button: ButtonPosition) => {
+    deleteButton: (uuid: string) => {
         set((state: ConnectionsState) => {
-            const index = state.buttons.findIndex(b => b.uuid === button.uuid);
-            state.buttons.splice(index, 1);
+            Array.from(state.buttons.entries())
+                .filter(value => value[1].uuid !== uuid)
+                .forEach(value => state.buttons.set(value[0], value[1]));
+            state.buttons.delete(uuid)
             return state;
         })
     },
diff --git a/karavan-designer/src/designer/route/DslConnections.tsx b/karavan-designer/src/designer/route/DslConnections.tsx
index a4eef251..934fd89c 100644
--- a/karavan-designer/src/designer/route/DslConnections.tsx
+++ b/karavan-designer/src/designer/route/DslConnections.tsx
@@ -14,15 +14,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React, {useEffect} from 'react';
+import React, {JSX, useEffect} from 'react';
 import '../karavan.css';
-import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
 import {ButtonPosition, DslPosition, EventBus} from "../utils/EventBus";
 import {CamelUi} from "../utils/CamelUi";
 import {useConnectionsStore, useDesignerStore, useIntegrationStore} from "../DesignerStore";
 import {shallow} from "zustand/shallow";
 import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
 import {TopologyUtils} from "karavan-core/lib/api/TopologyUtils";
+import {CamelElement} from "../../../../karavan-core/lib/model/IntegrationDefinition";
 
 const overlapGap: number = 40;
 
@@ -45,15 +45,17 @@ export function DslConnections() {
     });
 
     useEffect(() => {
-        const toDelete: string[] = Array.from(steps.keys()).filter(k => CamelDefinitionApiExt.findElementInIntegration(integration, k) === undefined);
-        toDelete.forEach(key => deleteStep(key));
+        const toDelete1: string[] = Array.from(steps.keys()).filter(k => CamelDefinitionApiExt.findElementInIntegration(integration, k) === undefined);
+        toDelete1.forEach(key => deleteStep(key));
+        const toDelete2: string[] = Array.from(buttons.keys()).filter(k => CamelDefinitionApiExt.findElementInIntegration(integration, k) === undefined);
+        toDelete2.forEach(key => deleteButton(key));
     }, [integration]);
 
     function setButtonPosition(btn: ButtonPosition) {
         if (btn.command === "add") {
-            addButton(btn);
+            addButton(btn.uuid, btn);
         } else if (btn.command === "delete") {
-            deleteButton(btn);
+            deleteButton(btn.uuid);
         } else if (btn.command === "clean") {
             clearButtons();
         }
@@ -103,8 +105,6 @@ export function DslConnections() {
             return (
                 <g key={pos.step.uuid + "-incoming"}>
                     <circle cx={incomingX} cy={fromY} r={r} className="circle-incoming"/>
-                    {/*<image x={imageX} y={imageY} href={CamelUi.getConnectionIconString(pos.step)} className="icon"/>*/}
-                    {/*<text x={imageX - 5} y={imageY + 40} className="caption" textAnchor="start">{CamelUi.getTitle(pos.step)}</text>*/}
                     <path d={`M ${lineX1},${lineY1} C ${lineX1},${lineY2} ${lineX2},${lineY1}  ${lineX2},${lineY2}`}
                           className="path-incoming" markerEnd="url(#arrowhead)"/>
                 </g>
@@ -189,8 +189,6 @@ export function DslConnections() {
             return (
                 <g key={pos.step.uuid + "-outgoing"}>
                     <circle cx={outgoingX} cy={outgoingY} r={r} className="circle-outgoing"/>
-                    {/*<image x={imageX} y={imageY} href={image} className="icon"/>*/}
-                    {/*<text x={imageX + 25} y={imageY + 40}  className="caption" textAnchor="end">{CamelUi.getOutgoingTitle(pos.step)}</text>*/}
                     <path
                         d={`M ${lineX1},${lineY1} C ${lineXi - 20}, ${lineY1} ${lineX1 - 15},${lineYi} ${lineXi},${lineYi} L ${lineX2},${lineY2}`}
                         className="path-incoming" markerEnd="url(#arrowhead)"/>
@@ -226,79 +224,105 @@ export function DslConnections() {
         )
     }
 
-    function hasSteps(step: CamelElement): boolean {
-        return (step.hasSteps() && !['FromDefinition'].includes(step.dslName))
-            || ['RouteDefinition', 'TryDefinition', 'ChoiceDefinition', 'SwitchDefinition'].includes(step.dslName);
+    function getNext(pos: DslPosition): CamelElement | undefined {
+        if (pos.nextstep) {
+            return pos.nextstep;
+        } else if (pos.parent) {
+            const parent = steps.get(pos.parent.uuid);
+            if (parent) return getNext(parent);
+        }
+    }
+
+    function isSpecial(pos: DslPosition): boolean {
+        return ['ChoiceDefinition', 'MulticastDefinition', 'TryDefinition'].includes(pos.step.dslName);
     }
 
-    function getPreviousStep(pos: DslPosition) {
-        return Array.from(steps.values())
-            .filter(p => pos.parent?.uuid === p.parent?.uuid)
-            .filter(p => p.inSteps)
-            .filter(p => p.position === pos.position - 1)[0];
+    function addArrowToList(list: JSX.Element[], from?: DslPosition, to?: DslPosition, fromHeader?: boolean, toHeader?: boolean): JSX.Element[]  {
+        const result: JSX.Element[] = [...list];
+        if (from && to) {
+            const rect1 = fromHeader === true ? from.headerRect : from.rect;
+            const rect2 = toHeader === true ? to.headerRect : to.rect;
+            result.push(getComplexArrow(from.step.uuid + "->" + to.step.uuid, rect1, rect2, toHeader === true));
+        }
+        return result;
     }
 
-    function getArrow(pos: DslPosition) {
-        const endX = pos.headerRect.x + pos.headerRect.width / 2 - left;
-        const endY = pos.headerRect.y - 9 - top;
-        if (pos.parent) {
+    function getArrow(pos: DslPosition): JSX.Element[] {
+        const list: JSX.Element[] = [];
+
+        if (pos.parent && pos.parent.dslName === 'FromDefinition' && pos.position === 0) {
+            // const parent = steps.get(pos.parent.uuid);
+            // list.push(...addArrowToList(list, parent, pos, true, false))
+        } else if (pos.parent && pos.parent.dslName === 'TryDefinition' && pos.position === 0) {
+            const parent = steps.get(pos.parent.uuid);
+            list.push(...addArrowToList(list, parent, pos, true, false))
+        } else if (pos.parent && ['CatchDefinition', 'FinallyDefinition'].includes(pos.parent.dslName)  && pos.position === 0) {
+            const parent = steps.get(pos.parent.uuid);
+            list.push(...addArrowToList(list, parent, pos, true, true))
+        } else if (pos.parent && pos.parent.dslName === 'MulticastDefinition') {
+            const parent = steps.get(pos.parent.uuid);
+            list.push(...addArrowToList(list, parent, pos, true, false))
+            if (parent?.nextstep) {
+                const to = steps.get(parent.nextstep.uuid);
+                list.push(...addArrowToList(list, pos, to, true, true))
+            }
+        } else if (pos.parent && pos.parent.dslName === 'ChoiceDefinition') {
             const parent = steps.get(pos.parent.uuid);
-            const showArrow = pos.prevStep !== undefined && !['TryDefinition', 'ChoiceDefinition'].includes(pos.prevStep.dslName);
-            const name = pos.prevStep?.dslName;
-            if (parent && showArrow) {
-                if ((!pos.inSteps || (pos.inSteps && pos.position === 0)) && parent.step.dslName !== 'MulticastDefinition') {
-                    return getArrows(pos);
-                } else if (parent.step.dslName === 'MulticastDefinition' && pos.inSteps) {
-                    return getArrows(pos)
-                } else if (pos.inSteps && pos.position > 0 && !hasSteps(pos.step)) {
-                    const prev = getPreviousStep(pos);
-                    if (prev) {
-                        const r = hasSteps(prev.step) ? prev.rect : prev.headerRect;
-                        const prevX = r.x + r.width / 2 - left;
-                        const prevY = r.y + r.height - top;
-                        return (
-                            <line name={name} x1={prevX} y1={prevY} x2={endX} y2={endY} className="path"
-                                  key={pos.step.uuid} markerEnd="url(#arrowhead)"/>
-                        )
-                    }
-                } else if (pos.inSteps && pos.position > 0 && hasSteps(pos.step)) {
-                    const prev = getPreviousStep(pos);
-                    if (prev) {
-                        const r = hasSteps(prev.step) ? prev.rect : prev.headerRect;
-                        const prevX = r.x + r.width / 2 - left;
-                        const prevY = r.y + r.height - top;
-                        return (
-                            <line name={name} x1={prevX} y1={prevY} x2={endX} y2={endY} className="path"
-                                  key={pos.step.uuid} markerEnd="url(#arrowhead)"/>
-                        )
-                    }
+            list.push(...addArrowToList(list, parent, pos, true, false))
+        } else if (pos.parent && ['WhenDefinition', 'OtherwiseDefinition', 'CatchDefinition', 'FinallyDefinition'].includes(pos.parent.dslName)) {
+            if (pos.position === 0) {
+                const parent = steps.get(pos.parent.uuid);
+                list.push(...addArrowToList(list, parent, pos, true, false))
+            }
+            if (pos.position === (pos.inStepsLength - 1) && !isSpecial(pos)) {
+                const nextElement = getNext(pos);
+                if (nextElement) {
+                    const next = steps.get(nextElement.uuid);
+                    list.push(...addArrowToList(list, pos, next, true, true))
                 }
             }
+        } else if (pos.step && !isSpecial(pos)) {
+            if (pos.nextstep) {
+                const next = steps.get(pos.nextstep.uuid);
+                const fromHeader = !pos.step.hasSteps();
+                list.push(...addArrowToList(list, pos, next, fromHeader, true))
+            }
+            if (pos.step.hasSteps() && (pos.step as any).steps.length > 0) {
+                const firstStep = (pos.step as any).steps[0];
+                const next = steps.get(firstStep.uuid);
+                list.push(...addArrowToList(list, pos, next, true, true))
+            }
         }
-    }
 
-    function getArrows(pos: DslPosition) {
-        if (pos.parent) {
-            const parent = steps.get(pos?.parent.uuid);
-            if (parent) {
-            const rect1 = parent.headerRect;
-            const rect2 = pos.headerRect;
-            return getComplexArrow(pos.step.uuid + ":" + pos.parent.uuid, rect1, rect2);
+        if (['WhenDefinition', 'OtherwiseDefinition'].includes(pos.step.dslName) && pos.step.hasSteps() && (pos.step as any).steps.length === 0) {
+            if (pos.nextstep) {
+                const to = steps.get(pos.nextstep.uuid);
+                list.push(...addArrowToList(list, pos, to, true, true))
+            } else {
+                const next = getNext(pos);
+                if (next) {
+                    const to = steps.get(next.uuid);
+                    list.push(...addArrowToList(list, pos, to, true, true))
+                }
             }
         }
-    }
 
-    function getButtonArrow(btn: ButtonPosition) {
-        const rect1 = btn.rect;
-        const uuid = btn.nextstep.uuid;
-        const nextStep = steps.get(uuid);
-        const rect2 = nextStep?.rect;
-        if (rect1 && rect2) {
-            return getComplexArrow(uuid + "-" + btn.nextstep.uuid, rect1, rect2);
+        if (pos.parent?.dslName === 'TryDefinition' && pos.inSteps && pos.position === (pos.inStepsLength - 1)) {
+            const parent = steps.get(pos.parent.uuid);
+            if (parent && parent.nextstep) {
+                const to = steps.get(parent.nextstep.uuid);
+                list.push(...addArrowToList(list, pos, to, true, true))
+            }
         }
+
+        if (!isSpecial(pos) && pos.inSteps && pos.nextstep && !pos.step.hasSteps() && pos.parent?.dslName !== 'MulticastDefinition') {
+            const to = steps.get(pos.nextstep.uuid);
+            list.push(...addArrowToList(list, pos, to, true, true))
+        }
+        return list;
     }
 
-    function getComplexArrow(key: string, rect1: DOMRect, rect2: DOMRect) {
+    function getComplexArrow(key: string, rect1: DOMRect, rect2: DOMRect, toHeader: boolean) {
             const startX = rect1.x + rect1.width / 2 - left;
             const startY = rect1.y + rect1.height - top - 2;
             const endX = rect2.x + rect2.width / 2 - left;
@@ -309,7 +333,7 @@ export function DslConnections() {
 
             const radX = gapX > 30 ? 20 : gapX/2;
             const radY = gapY > 30 ? 20 : gapY/2;
-            const endY = rect2.y - top - 9 - radY;
+            const endY = rect2.y - top - radY - (toHeader ? 9 : 6);
 
             const iRadX = startX > endX ? -1 * radX : radX;
             const iRadY = startY > endY ? -1 * radY : radY;
@@ -336,7 +360,7 @@ export function DslConnections() {
                 + ` L ${LX2} ${LY2}`
                 + ` Q ${Q2_X1} ${Q2_Y1} ${Q2_X2} ${Q2_Y2}`
             return (
-                <path key={key} d={path} className="path" markerEnd="url(#arrowhead)"/>
+                <path key={key} name={key} d={path} className="path" markerEnd="url(#arrowhead)"/>
             )
     }
 
@@ -353,11 +377,11 @@ export function DslConnections() {
                     </marker>
                 </defs>
                 {stepsArray.map(pos => getCircle(pos))}
-                {stepsArray.map(pos => getArrow(pos))}
-                {buttons.map(btn => getButtonArrow(btn)).filter(b => b !== undefined)}
+                <g>
+                    {stepsArray.map(pos => getArrow(pos)).flat(1)}
+                </g>
                 {getIncomings().map(p => getIncoming(p))}
                 {getOutgoings().map(p => getOutgoing(p))}
-                {/*{getInternals().map((p) => getInternalLines(p)).flat()}*/}
             </svg>
         )
     }
diff --git a/karavan-designer/src/designer/route/RouteDesigner.tsx b/karavan-designer/src/designer/route/RouteDesigner.tsx
index b6b1c942..b02ed20b 100644
--- a/karavan-designer/src/designer/route/RouteDesigner.tsx
+++ b/karavan-designer/src/designer/route/RouteDesigner.tsx
@@ -147,16 +147,17 @@ export function RouteDesigner() {
                      data-click="FLOWS"
                      onClick={event => {unselectElement(event)}}
                      ref={flowRef}>
-                    {routeConfigurations?.map((routeConfiguration, index: number) => (
+                    {routeConfigurations?.map((routeConfiguration, index: number, array) => (
                         <DslElement key={routeConfiguration.uuid}
                                     inSteps={false}
                                     position={index}
                                     step={routeConfiguration}
                                     nextStep={undefined}
                                     prevStep={undefined}
+                                    inStepsLength={array.length}
                                     parent={undefined}/>
                     ))}
-                    {routes?.map((route: any, index: number) => {
+                    {routes?.map((route: any, index: number, array) => {
                         return (
                             <DslElement key={route.uuid}
                                         inSteps={false}
@@ -164,6 +165,7 @@ export function RouteDesigner() {
                                         step={route}
                                         nextStep={undefined}
                                         prevStep={undefined}
+                                        inStepsLength={array.length}
                                         parent={undefined}/>
                         )
                     })}
diff --git a/karavan-designer/src/designer/route/element/DslElement.css b/karavan-designer/src/designer/route/element/DslElement.css
index 7af5bd51..61cc5af3 100644
--- a/karavan-designer/src/designer/route/element/DslElement.css
+++ b/karavan-designer/src/designer/route/element/DslElement.css
@@ -18,14 +18,19 @@
 .karavan .dsl-page .flows .step-element .header-route {
     display: block;
     background: transparent;
-    padding: 10px;
+    border-radius: 42px;
+    padding: 20px;
     margin: 0;
     z-index: 101;
     min-width: 260px;
 }
 
-.karavan .dsl-page .flows .step-element .header-bottom-line {
-    border-bottom: 1px dashed;
+.karavan .dsl-page .flows .step-element .header-bottom-selected {
+    border-bottom: 1px dashed var(--step-border-color-selected);
+}
+
+.karavan .dsl-page .flows .step-element .header-bottom-not-selected {
+    border-bottom: 1px dashed var(--pf-v5-global--Color--200);
 }
 
 .karavan .dsl-page .flows .step-element .header-route:hover {
@@ -48,7 +53,7 @@
 .karavan .step-element .header .delete-button,
 .element-builder .header .delete-button {
     position: absolute;
-    top: -7px;
+    top: -11px;
     line-height: 1;
     border: 0;
     padding: 0;
@@ -72,10 +77,6 @@
     height: 50px;
 }
 
-.karavan .step-element-selected {
-    background-color: rgba(var(--pf-v5-global--palette--blue-50), 1);
-}
-
 .karavan .step-element .selected .header-icon {
     border-color: var(--pf-v5-global--primary-color--100);
     background-color: var(--pf-v5-global--palette--blue-50);
@@ -152,7 +153,7 @@
 
 .karavan .step-element .insert-element-button {
     position: absolute;
-    top: -7px;
+    top: -11px;
     line-height: 1;
     border: 0;
     padding: 0;
diff --git a/karavan-designer/src/designer/route/element/DslElement.tsx b/karavan-designer/src/designer/route/element/DslElement.tsx
index a1b63a98..8f89a0f9 100644
--- a/karavan-designer/src/designer/route/element/DslElement.tsx
+++ b/karavan-designer/src/designer/route/element/DslElement.tsx
@@ -27,6 +27,7 @@ import {shallow} from "zustand/shallow";
 import {useRouteDesignerHook} from "../useRouteDesignerHook";
 import {AddElementIcon} from "./DslElementIcons";
 import {DslElementHeader} from "./DslElementHeader";
+import {TryDefinition} from "karavan-core/lib/model/CamelDefinition";
 
 interface Props {
     step: CamelElement,
@@ -35,12 +36,12 @@ interface Props {
     prevStep: CamelElement | undefined,
     inSteps: boolean
     position: number
+    inStepsLength: number
 }
 
 export function DslElement(props: Props) {
 
     const headerRef = React.useRef<HTMLDivElement>(null);
-    const addButtonRef = React.useRef<HTMLDivElement>(null);
     const {
         selectElement,
         moveElement,
@@ -94,10 +95,6 @@ export function DslElement(props: Props) {
         return selectedUuids.includes(props.step.uuid);
     }
 
-    function isElementHidden(): boolean {
-        return props.step.dslName === 'LogDefinition' && hideLogDSL;
-    }
-
     function hasBorder(): boolean {
         const step = props.step;
         if (['FilterDefinition', 'RouteDefinition', 'RouteConfigurationDefinition'].includes(step.dslName)) {
@@ -106,6 +103,7 @@ export function DslElement(props: Props) {
         if ([
             'FromDefinition',
             'TryDefinition',
+            'MulticastDefinition',
             'CatchDefinition', 'FinallyDefinition',
             'ChoiceDefinition',
             'SwitchDefinition', 'WhenDefinition', 'OtherwiseDefinition'
@@ -119,10 +117,6 @@ export function DslElement(props: Props) {
         return ['FromDefinition', 'RouteConfigurationDefinition', 'RouteDefinition', 'WhenDefinition', 'OtherwiseDefinition'].includes(props.step.dslName);
     }
 
-    function isRoute(): boolean {
-        return ['RouteDefinition'].includes(props.step.dslName);
-    }
-
     function isAddStepButtonLeft(): boolean {
         return ['MulticastDefinition']
             .includes(props.step.dslName);
@@ -139,28 +133,10 @@ export function DslElement(props: Props) {
         return children.filter((c: ChildElement) => c.name === 'steps' || c.multiple).length > 0 && props.inSteps;
     }
 
-    function sendButtonPosition(el: HTMLButtonElement | null) {
-        const {nextStep, step, parent} = props;
-        let needArrow = !hasBorder() && !['ChoiceDefinition', 'MulticastDefinition', 'TryDefinition'].includes(step.dslName);
-
-        if (parent
-            && ['TryDefinition'].includes(parent.dslName)
-            && !['CatchDefinition', 'FinallyDefinition'].includes(step.dslName)) {
-            needArrow = true;
-        }
-
-        if (el && nextStep && needArrow) {
-            const rect = headerRef.current?.getBoundingClientRect();
-
-            if (rect)
-                EventBus.sendButtonPosition("add", step.uuid, nextStep, rect);
-        }
-    }
 
     function sendPosition(el: HTMLDivElement | null) {
-        const {step, prevStep, parent} = props;
+        const {step, prevStep, nextStep, parent, inSteps, inStepsLength} = props;
         const isSelected = isElementSelected();
-        const isHidden = isElementHidden();
         if (el) {
             const header = Array.from(el.childNodes.values()).filter((n: any) => n.classList.contains("header"))[0];
             if (header) {
@@ -168,13 +144,9 @@ export function DslElement(props: Props) {
                 const headerRect = headerIcon.getBoundingClientRect();
                 const rect = el.getBoundingClientRect();
                 if (step.showChildren) {
-                    if (isHidden) {
-                        EventBus.sendPosition("add", step, prevStep, parent, rect, headerRect, props.position, props.inSteps, isSelected);
-                    } else {
-                        EventBus.sendPosition("add", step, prevStep, parent, rect, headerRect, props.position, props.inSteps, isSelected);
-                    }
+                    EventBus.sendPosition("add", step, prevStep, nextStep, parent, rect, headerRect, props.position, inStepsLength, inSteps, isSelected);
                 } else {
-                    EventBus.sendPosition("delete", step, prevStep, parent, new DOMRect(), new DOMRect(), 0);
+                    EventBus.sendPosition("delete", step, prevStep, nextStep, parent, new DOMRect(), new DOMRect(), 0, 0);
                 }
             }
         }
@@ -190,7 +162,6 @@ export function DslElement(props: Props) {
 
     function getChildrenElementsStyle(child: ChildElement, notOnlySteps: boolean) {
         const style: CSSProperties = {
-            // borderStyle: isBorder ? "dotted" : "none",
             borderColor: "var(--step-border-color)",
             borderWidth: "1px",
             borderRadius: "16px",
@@ -229,10 +200,12 @@ export function DslElement(props: Props) {
             return (
                 <div className={child.name + " has-child"} style={getChildrenElementsStyle(child, notOnlySteps)}
                      key={step.uuid + "-child-" + index}>
-                    {children.map((element, index) => {
+                    {children.map((element, index, array) => {
                             let prevStep = children.at(index - 1);
                             let nextStep: CamelElement | undefined = undefined;
-                            if (['TryDefinition', 'ChoiceDefinition'].includes(step.dslName)) {
+                            if ('ChoiceDefinition' === step.dslName) {
+                                nextStep = props.nextStep;
+                            } else if ('TryDefinition' === step.dslName && ['CatchDefinition', 'FinallyDefinition'].includes(element.dslName)) {
                                 nextStep = props.nextStep;
                             } else {
                                 nextStep = children.at(index + 1);
@@ -244,6 +217,7 @@ export function DslElement(props: Props) {
                                     step={element}
                                     nextStep={nextStep}
                                     prevStep={prevStep}
+                                    inStepsLength={array.length}
                                     parent={step}/>
                             </div>)
                         }
@@ -266,12 +240,10 @@ export function DslElement(props: Props) {
         const hideAddButton = step.dslName === 'StepDefinition' && !CamelDisplayUtil.isStepDefinitionExpanded(integration, step.uuid, selectedUuids.at(0));
         if (hideAddButton) return (<></>)
         else return (
-            <div ref={addButtonRef}>
-                <Tooltip position={"bottom"}
+                <Tooltip position={"left"}
                          content={<div>{"Add step to " + CamelDisplayUtil.getTitle(step)}</div>}
                 >
                     <button type="button"
-                            ref={el => sendButtonPosition(el)}
                             aria-label="Add"
                             onClick={e => onOpenSelector(e)}
                             className={isAddStepButtonLeft() ? "add-button add-button-left" : "add-button add-button-bottom"}>
@@ -279,13 +251,12 @@ export function DslElement(props: Props) {
                     </button>
 
                 </Tooltip>
-            </div>
         )
     }
 
     const element: CamelElement = props.step;
     const className = "step-element"
-        + (isElementSelected() ? " step-element-selected" : "") + (!props.step.showChildren ? " hidden-step" : "")
+        + (!props.step.showChildren ? " hidden-step" : "")
         + ((element as any).disabled ? " disabled " : "");
     return (
         <div key={"root" + element.uuid}
diff --git a/karavan-designer/src/designer/route/element/DslElementHeader.tsx b/karavan-designer/src/designer/route/element/DslElementHeader.tsx
index 9ac4ab99..d201d105 100644
--- a/karavan-designer/src/designer/route/element/DslElementHeader.tsx
+++ b/karavan-designer/src/designer/route/element/DslElementHeader.tsx
@@ -20,7 +20,6 @@ import '../../karavan.css';
 import './DslElement.css';
 import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
 import {CamelUi} from "../../utils/CamelUi";
-import {EventBus} from "../../utils/EventBus";
 import {ChildElement, CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
 import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
 import {CamelDisplayUtil} from "karavan-core/lib/api/CamelDisplayUtil";
@@ -28,11 +27,7 @@ import {useDesignerStore} from "../../DesignerStore";
 import {shallow} from "zustand/shallow";
 import {useRouteDesignerHook} from "../useRouteDesignerHook";
 import {AddElementIcon, DeleteElementIcon, InsertElementIcon} from "./DslElementIcons";
-import {
-    InterceptDefinition,
-    InterceptFromDefinition,
-    InterceptSendToEndpointDefinition, OnCompletionDefinition, OnExceptionDefinition, RouteConfigurationDefinition
-} from "karavan-core/lib/model/CamelDefinition";
+import { RouteConfigurationDefinition} from "karavan-core/lib/model/CamelDefinition";
 
 interface Props {
     headerRef: React.RefObject<HTMLDivElement>
@@ -79,12 +74,8 @@ export function DslElementHeader(props: Props) {
         return selectedUuids.includes(props.step.uuid);
     }
 
-    function isElementHidden(): boolean {
-        return props.step.dslName === 'LogDefinition' && hideLogDSL;
-    }
-
     function isWide(): boolean {
-        return ['RouteConfigurationDefinition', 'RouteDefinition', 'ChoiceDefinition', 'SwitchDefinition', 'MulticastDefinition', 'TryDefinition', 'CircuitBreakerDefinition']
+        return ['RouteConfigurationDefinition', 'RouteDefinition', 'ChoiceDefinition', 'MulticastDefinition', 'TryDefinition', 'CircuitBreakerDefinition']
             .includes(props.step.dslName);
     }
 
@@ -129,29 +120,6 @@ export function DslElementHeader(props: Props) {
         return style;
     }
 
-    function sendPosition(el: HTMLDivElement | null) {
-        const {step, prevStep, parent} = props;
-        const isSelected = isElementSelected();
-        const isHidden = isElementHidden();
-        if (el) {
-            const header = Array.from(el.childNodes.values()).filter((n: any) => n.classList.contains("header"))[0];
-            if (header) {
-                const headerIcon: any = Array.from(header.childNodes.values()).filter((n: any) => n.classList.contains("header-icon"))[0];
-                const headerRect = headerIcon.getBoundingClientRect();
-                const rect = el.getBoundingClientRect();
-                if (step.showChildren) {
-                    if (isHidden) {
-                        EventBus.sendPosition("add", step, prevStep, parent, rect, headerRect, props.position, props.inSteps, isSelected);
-                    } else {
-                        EventBus.sendPosition("add", step, prevStep, parent, rect, headerRect, props.position, props.inSteps, isSelected);
-                    }
-                } else {
-                    EventBus.sendPosition("delete", step, prevStep, parent, new DOMRect(), new DOMRect(), 0);
-                }
-            }
-        }
-    }
-
     function getAvailableModels() { // TODO: make static list-of-values instead
         const step: CamelElement = props.step
         return CamelUi.getSelectorModelsForParent(step.dslName, false);
@@ -174,10 +142,13 @@ export function DslElementHeader(props: Props) {
         const classes: string[] = [];
         const step: CamelElement = props.step;
         if (step.dslName === 'RouteDefinition') {
-            classes.push(...'header-route', 'header-bottom-line')
+            classes.push('header-route')
+            classes.push('header-bottom-line')
+            classes.push(isElementSelected() ? 'header-bottom-selected' : 'header-bottom-not-selected')
         } else if (step.dslName === 'RouteConfigurationDefinition') {
             classes.push('header-route')
             if (hasElements(step)) classes.push('header-bottom-line')
+            classes.push(isElementSelected() ? 'header-bottom-selected' : 'header-bottom-not-selected')
         } else {
             classes.push('header')
         }
@@ -200,7 +171,6 @@ export function DslElementHeader(props: Props) {
             <div className={"dsl-element " + headerClasses} style={getHeaderStyle()} ref={props.headerRef}>
                 {!['RouteConfigurationDefinition', 'RouteDefinition'].includes(props.step.dslName) &&
                     <div
-                        ref={el => sendPosition(el)}
                         className={"header-icon"}
                         style={isWide() ? {width: ""} : {}}>
                         {CamelUi.getIconForElement(step)}
diff --git a/karavan-designer/src/designer/route/useRouteDesignerHook.tsx b/karavan-designer/src/designer/route/useRouteDesignerHook.tsx
index b65966a0..0f90bb10 100644
--- a/karavan-designer/src/designer/route/useRouteDesignerHook.tsx
+++ b/karavan-designer/src/designer/route/useRouteDesignerHook.tsx
@@ -105,7 +105,7 @@ export function useRouteDesignerHook () {
     }
 
     const deleteElement = () =>  {
-        EventBus.sendPosition("clean", new CamelElement(""), undefined, undefined, new DOMRect(), new DOMRect(), 0);
+        EventBus.sendPosition("clean", new CamelElement(""), undefined,undefined, undefined, new DOMRect(), new DOMRect(), 0, 0);
         EventBus.sendButtonPosition("clean", '',  new CamelElement(""), new DOMRect());
         let i = integration;
         selectedUuids.forEach(uuidToDelete => {
diff --git a/karavan-designer/src/designer/utils/EventBus.ts b/karavan-designer/src/designer/utils/EventBus.ts
index 55906f0f..2d075b00 100644
--- a/karavan-designer/src/designer/utils/EventBus.ts
+++ b/karavan-designer/src/designer/utils/EventBus.ts
@@ -38,10 +38,12 @@ export class ButtonPosition {
 export class DslPosition {
     step: CamelElement = new CamelElement("");
     prevStep: CamelElement | undefined;
+    nextstep: CamelElement | undefined;
     parent: CamelElement | undefined;
     inSteps: boolean = false;
     isSelected: boolean = false;
     position: number = 0;
+    inStepsLength: number = 0;
     rect: DOMRect = new DOMRect();
     headerRect: DOMRect = new DOMRect();
     command: "add" | "delete" | "clean" = "add";
@@ -49,20 +51,24 @@ export class DslPosition {
     constructor(command: "add" | "delete" | "clean",
                 step: CamelElement,
                 prevStep: CamelElement | undefined,
+                nextstep: CamelElement | undefined,
                 parent:CamelElement | undefined,
                 rect: DOMRect,
                 headerRect:DOMRect,
                 position: number,
+                inStepsLength: number,
                 inSteps: boolean = false,
                 isSelected: boolean = false) {
         this.command = command;
         this.step = step;
+        this.nextstep = nextstep;
         this.prevStep = prevStep;
         this.parent = parent;
         this.rect = rect;
         this.headerRect = headerRect;
         this.inSteps = inSteps;
         this.position = position;
+        this.inStepsLength = inStepsLength;
         this.isSelected = isSelected;
     }
 }
@@ -110,12 +116,15 @@ export const EventBus = {
     sendPosition: (command: "add" | "delete" | "clean",
                    step: CamelElement,
                    prevStep: CamelElement | undefined,
+                   nextstep: CamelElement | undefined,
                    parent: CamelElement | undefined,
                    rect: DOMRect,
                    headerRect: DOMRect,
                    position: number,
+                   inStepsLength: number,
                    inSteps: boolean = false,
-                   isSelected: boolean = false) => dslPositions.next(new DslPosition(command, step, prevStep, parent, rect, headerRect, position, inSteps, isSelected)),
+                   isSelected: boolean = false) => dslPositions.next(
+                       new DslPosition(command, step, prevStep, nextstep, parent, rect, headerRect, position, inStepsLength, inSteps, isSelected)),
     onPosition: () => dslPositions.asObservable(),
 
     sendButtonPosition: (command: "add" | "delete" | "clean", uuid: string,