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/08/21 22:22:27 UTC

[camel-karavan] branch feature-836 created (now c60f3ed4)

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

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


      at c60f3ed4 Resize works

This branch includes the following new commits:

     new e4e70e8f First try
     new a2c1b127 Scroll works
     new c60f3ed4 Resize works

The 3 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.



[camel-karavan] 03/03: Resize works

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit c60f3ed4b915acccd871eec12a4b8edb9c7a7a7d
Author: Marat Gubaidullin <ma...@Marats-MacBook-Pro.local>
AuthorDate: Mon Aug 21 18:22:07 2023 -0400

    Resize works
---
 karavan-designer/src/App.tsx                       | 16 ++++--
 .../src/designer/route/DslConnections.tsx          |  9 ++-
 .../src/designer/route/RouteDesigner.tsx           | 64 +++++++++++++++-------
 ...Observer.tsx => useDrawerMutationsObserver.tsx} | 20 +++----
 .../src/designer/route/useResizeObserver.tsx       | 14 ++---
 5 files changed, 74 insertions(+), 49 deletions(-)

diff --git a/karavan-designer/src/App.tsx b/karavan-designer/src/App.tsx
index 4e751d60..6c31206b 100644
--- a/karavan-designer/src/App.tsx
+++ b/karavan-designer/src/App.tsx
@@ -145,7 +145,7 @@ class App extends React.Component<Props, State> {
             new MenuItem("designer", "Designer", <BlueprintIcon/>),
             new MenuItem("knowledgebase", "Knowledgebase", <KnowledgebaseIcon/>),
         ]
-        return (<Flex className="nav-buttons" direction={{default: "column"}} //style={{height: "100%"}}
+        return (<Flex className="nav-buttons" direction={{default: "column"}} style={{height: "100%"}}
                       spaceItems={{default: "spaceItemsNone"}}>
             <FlexItem alignSelf={{default: "alignSelfCenter"}}>
                 <Tooltip className="logo-tooltip" content={"Apache Camel Karavan"}
@@ -202,7 +202,7 @@ class App extends React.Component<Props, State> {
     public render() {
         const {loaded} = this.state;
         return (
-            <Page className="karavan" header={this.getHeader()} sidebar={this.pageNav()}>
+            <Page className="karavan">
                 <AlertGroup isToast isLiveRegion>
                     {this.state.alerts.map((e: ToastMessage) => (
                         // @ts-ignore
@@ -213,8 +213,16 @@ class App extends React.Component<Props, State> {
                         </Alert>
                     ))}
                 </AlertGroup>
-                {loaded !== true && this.getSpinner()}
-                {loaded === true && this.getPage()}
+                <Flex direction={{default: "row"}} style={{width: "100%", height: "100%"}}
+                      alignItems={{default: "alignItemsStretch"}} spaceItems={{default: 'spaceItemsNone'}}>
+                    <FlexItem>
+                        {this.pageNav()}
+                    </FlexItem>
+                    <FlexItem flex={{default: "flex_2"}} style={{height: "100%"}}>
+                        {loaded !== true && this.getSpinner()}
+                        {loaded === true && this.getPage()}
+                </FlexItem>
+                </Flex>
             </Page>
         )
     }
diff --git a/karavan-designer/src/designer/route/DslConnections.tsx b/karavan-designer/src/designer/route/DslConnections.tsx
index 7a618057..3b508ae3 100644
--- a/karavan-designer/src/designer/route/DslConnections.tsx
+++ b/karavan-designer/src/designer/route/DslConnections.tsx
@@ -443,7 +443,7 @@ export const DslConnections = () => {
         const stepsArray = Array.from(steps.values());
         return (
             <svg
-                style={{width: width - 5, height: height - 5, position: "absolute", left: 0, top: 0}}
+                style={{width: width, height: height, position: "absolute", left: 0, top: 0}}
                 viewBox={"0 0 " + (width - 5) + " " + (height -5)}>
                 <defs>
                     <marker id="arrowhead" markerWidth="9" markerHeight="6" refX="0" refY="3" orient="auto" className="arrow">
@@ -461,8 +461,11 @@ export const DslConnections = () => {
 
     console.log("RENDER CONNECTION")
     return (
-        <div style={{position: "absolute", width: width + 5, height: height + 80, top: 0, left: 0, background: "red"}}>
-            {/*{getSvg()}*/}
+        <div
+            id="connections"
+            style={{position: "absolute", width: width, height: height + 80, top: 0, left: 0, background: "red"}}
+        >
+            {getSvg()}
             {/*{getIncomings().map(p => getIncomingIcons(p))}*/}
             {/*{getOutgoings().map(p => getOutgoingIcons(p))}*/}
         </div>
diff --git a/karavan-designer/src/designer/route/RouteDesigner.tsx b/karavan-designer/src/designer/route/RouteDesigner.tsx
index 4f460933..69be9a48 100644
--- a/karavan-designer/src/designer/route/RouteDesigner.tsx
+++ b/karavan-designer/src/designer/route/RouteDesigner.tsx
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React, {useCallback, useEffect, useRef, useState} from 'react';
+import React, {createRef, useCallback, useEffect, useRef, useState} from 'react';
 import {
     Drawer,
     DrawerPanelContent,
@@ -39,6 +39,8 @@ import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
 import {RouteConfigurationDefinition} from "karavan-core/lib/model/CamelDefinition";
 import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
 import useResizeObserver from "./useResizeObserver";
+import {DslPosition} from "../utils/EventBus";
+import useMutationsObserver from "./useDrawerMutationsObserver";
 
 interface Props {
     // onSave?: (integration: Integration, propertyOnly: boolean) => void
@@ -50,7 +52,6 @@ export const RouteDesigner = (props: Props) => {
 
     const printerRef = React.createRef()
     const contentRef: React.RefObject<HTMLDivElement> = useRef(null);
-    const flowRef: React.RefObject<HTMLDivElement> = useRef(null);
 
     const {
         deleteElement,
@@ -70,6 +71,36 @@ export const RouteDesigner = (props: Props) => {
             s.setShowSelector, s.setShowDeleteConfirmation, s.setPropertyOnly, s.setShowSteps, s.setDeleteMessage, s.setParentId, s.setSelectedUuids, s.setClipboardSteps, s.setPosition,
             s.width, s.height, s.top, s.left], shallow)
 
+    // const [refState, setRefState] = useState<Element | null>(null);
+
+    const onResize = useCallback((target: HTMLDivElement) => {
+        if (flowRef && flowRef.current) {
+            const el = flowRef.current.getBoundingClientRect();
+            if (width !== el.width || height !== el.height || top !== el.top || left !== el.left) {
+                setPosition(el.width, el.height, el.top, el.left)
+                console.log("elmRect", el)
+            }
+        }
+    }, []);
+
+    const onMutate = useCallback((target: HTMLDivElement, mutations: any) => {
+        if (flowRef && flowRef.current) {
+            const el = flowRef.current.getBoundingClientRect();
+            if (width !== el.width || height !== el.height || top !== el.top || left !== el.left) {
+                setPosition(el.width, el.height, el.top, el.left)
+                console.log("elmRect", el)
+            }
+        }
+    }, []);
+
+    const firstRef = useResizeObserver(onResize);
+    const secondRef = useMutationsObserver(onMutate);
+    const flowRef = useRef<HTMLDivElement | null>(null);
+    //
+    // useEffect(() => {
+    //     if (refState) console.log("useEffect RouteDesigner", refState);
+    // }, [refState]);
+
     // function componentDidMount() {
     //     logic.componentDidMount();
     // }
@@ -145,23 +176,13 @@ export const RouteDesigner = (props: Props) => {
         )
     }
 
-    // function onResizePage(el: HTMLDivElement | null) {
-    //     console.log("onResizePage", el)
-    //     const rect = el?.getBoundingClientRect();
-    //     if (el && rect && (el.scrollWidth !== width || el.scrollHeight !== height || rect.top !== top || rect.left !== left)) {
-    //         setPosition(el.scrollWidth, el.scrollHeight, rect.top, rect.left)
-    //     }
-    // }
-
-    const onResize = useCallback((target: HTMLDivElement) => {
-        const el = target.getBoundingClientRect();
-        if (width !== el.width || height !== el.height || top !== el.top || left !== el.left) {
-            setPosition(el.width, el.height, el.top, el.left)
-            console.log("elmRect", el)
+    function onRef(el: HTMLDivElement | null) {
+        // console.log("onRef", el)
+        const rect = el?.getBoundingClientRect();
+        if (el && rect && (rect.width !== width || rect.height !== height || rect.top !== top || rect.left !== left)) {
+            setPosition(rect.width, rect.height, rect.top, rect.left)
         }
-    }, []);
-
-    const ref = useResizeObserver(onResize);
+    }
 
     console.log("RENDER ROUTE_DESIGNER")
 
@@ -174,7 +195,8 @@ export const RouteDesigner = (props: Props) => {
                 <div id="flows" className="flows" data-click="FLOWS" onClick={event => {
                     // logic.unselectElement(event)
                 }}
-                     ref={ref}
+
+                     ref={flowRef}
                 >
                     {routeConfigurations?.map((routeConfiguration, index: number) => (
                         <DslElement key={routeConfiguration.uuid}
@@ -214,8 +236,8 @@ export const RouteDesigner = (props: Props) => {
 
     const hasFlows = integration?.spec?.flows?.length && integration?.spec?.flows?.length > 0;
     return (
-        <div className="dsl-page">
-            <div className="dsl-page-columns">
+        <div className="dsl-page" ref={firstRef}>
+            <div className="dsl-page-columns" ref={secondRef}>
                 <Drawer isExpanded isInline>
                     <DrawerContent panelContent={getPropertiesPanel()}>
                         <DrawerContentBody>{hasFlows && getGraph()}</DrawerContentBody>
diff --git a/karavan-designer/src/designer/route/useResizeObserver.tsx b/karavan-designer/src/designer/route/useDrawerMutationsObserver.tsx
similarity index 69%
copy from karavan-designer/src/designer/route/useResizeObserver.tsx
copy to karavan-designer/src/designer/route/useDrawerMutationsObserver.tsx
index 93141b20..f24a9e58 100644
--- a/karavan-designer/src/designer/route/useResizeObserver.tsx
+++ b/karavan-designer/src/designer/route/useDrawerMutationsObserver.tsx
@@ -17,29 +17,25 @@
 
 import { useLayoutEffect, useRef } from 'react';
 
-function useResizeObserver<T extends HTMLElement>(
-    callback: (target: T, entry: ResizeObserverEntry) => void
-) {
+function useMutationsObserver<T extends HTMLElement>(callback: (target: T, mutations: any) => void) {
     const ref = useRef<T>(null)
 
     useLayoutEffect(() => {
-        const element = ref?.current;
 
+        const element = ref?.current;
         if (!element) {
             return;
         }
-
-        const observer = new ResizeObserver((entries) => {
-            callback(element, entries[0]);
-        });
-
-        observer.observe(element);
+        const drawer = element.childNodes[0].childNodes[0].childNodes[1];
+        const observer2 = new MutationObserver(mutations => callback(element, mutations));
+        observer2.observe(drawer, {attributes: true, attributeOldValue: true, attributeFilter: ['style']});
         return () => {
-            observer.disconnect();
+            // observer1.disconnect();
+            observer2.disconnect();
         };
     }, [callback, ref]);
 
     return ref
 }
 
-export default useResizeObserver;
\ No newline at end of file
+export default useMutationsObserver;
\ No newline at end of file
diff --git a/karavan-designer/src/designer/route/useResizeObserver.tsx b/karavan-designer/src/designer/route/useResizeObserver.tsx
index 93141b20..27915bea 100644
--- a/karavan-designer/src/designer/route/useResizeObserver.tsx
+++ b/karavan-designer/src/designer/route/useResizeObserver.tsx
@@ -17,25 +17,21 @@
 
 import { useLayoutEffect, useRef } from 'react';
 
-function useResizeObserver<T extends HTMLElement>(
-    callback: (target: T, entry: ResizeObserverEntry) => void
-) {
+function useResizeObserver<T extends HTMLElement>(callback: (target: T, entry: ResizeObserverEntry) => void) {
     const ref = useRef<T>(null)
 
     useLayoutEffect(() => {
-        const element = ref?.current;
 
+        const element = ref?.current;
         if (!element) {
             return;
         }
-
-        const observer = new ResizeObserver((entries) => {
+        const observer1 = new ResizeObserver((entries) => {
             callback(element, entries[0]);
         });
-
-        observer.observe(element);
+        observer1.observe(element);
         return () => {
-            observer.disconnect();
+            observer1.disconnect();
         };
     }, [callback, ref]);
 


[camel-karavan] 01/03: First try

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit e4e70e8fddf9343e021c7105e7f7d7a0340ba9ff
Author: Marat Gubaidullin <ma...@Marats-MacBook-Pro.local>
AuthorDate: Mon Aug 21 10:09:21 2023 -0400

    First try
---
 karavan-designer/package-lock.json                 |  64 +++-
 karavan-designer/package.json                      |   3 +-
 karavan-designer/public/example/demo.camel.yaml    |  41 +++
 karavan-designer/src/App.tsx                       |   7 +-
 karavan-designer/src/designer/KaravanDesigner.tsx  | 133 +++----
 karavan-designer/src/designer/KaravanStore.ts      | 127 +++++++
 .../src/designer/route/DslConnections.tsx          | 250 +++++++------
 karavan-designer/src/designer/route/DslElement.tsx | 398 ++++++++++-----------
 .../src/designer/route/ElementResizeListener.tsx   |  75 ++++
 .../src/designer/route/RouteDesigner.tsx           | 260 +++++++-------
 .../src/designer/route/RouteDesignerLogic.tsx      | 396 --------------------
 .../designer/route/property/DslPropertyField.tsx   |   2 +-
 .../src/designer/route/useRouteDesignerHook.tsx    | 384 ++++++++++++++++++++
 karavan-designer/src/index.tsx                     |   8 +-
 14 files changed, 1206 insertions(+), 942 deletions(-)

diff --git a/karavan-designer/package-lock.json b/karavan-designer/package-lock.json
index a9956967..d6fbc8b5 100644
--- a/karavan-designer/package-lock.json
+++ b/karavan-designer/package-lock.json
@@ -9,7 +9,7 @@
       "version": "4.0.0-RC2",
       "license": "Apache-2.0",
       "dependencies": {
-        "@monaco-editor/react": "4.5.0",
+        "@monaco-editor/react": "4.5.1",
         "@patternfly/patternfly": "^5.0.2",
         "@patternfly/react-core": "^5.0.0",
         "@patternfly/react-table": "^5.0.0",
@@ -24,7 +24,8 @@
         "react": "18.2.0",
         "react-dom": "18.2.0",
         "rxjs": "7.8.1",
-        "uuid": "9.0.0"
+        "uuid": "9.0.0",
+        "zustand": "^4.4.1"
       },
       "devDependencies": {
         "@svgr/webpack": "^7.0.0",
@@ -36,7 +37,7 @@
         "@typescript-eslint/eslint-plugin": "^5.59.2",
         "@typescript-eslint/parser": "^5.59.2",
         "eslint": "^8.39.0",
-        "monaco-editor": "0.38.0",
+        "monaco-editor": "0.41.0",
         "react-scripts": "5.0.1",
         "typescript": "^4.9.5"
       }
@@ -3198,9 +3199,9 @@
       }
     },
     "node_modules/@monaco-editor/react": {
-      "version": "4.5.0",
-      "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.5.0.tgz",
-      "integrity": "sha512-VJMkp5Fe1+w8pLEq8tZPHZKu8zDXQIA1FtiDTSNccg1D3wg1YIZaH2es2Qpvop1k62g3c/YySRb3bnGXu2XwYQ==",
+      "version": "4.5.1",
+      "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.5.1.tgz",
+      "integrity": "sha512-NNDFdP+2HojtNhCkRfE6/D6ro6pBNihaOzMbGK84lNWzRu+CfBjwzGt4jmnqimLuqp5yE5viHS2vi+QOAnD5FQ==",
       "dependencies": {
         "@monaco-editor/loader": "^1.3.3"
       },
@@ -3996,7 +3997,7 @@
       "version": "15.7.5",
       "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
       "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==",
-      "dev": true
+      "devOptional": true
     },
     "node_modules/@types/qs": {
       "version": "6.9.7",
@@ -4014,7 +4015,7 @@
       "version": "18.2.0",
       "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.0.tgz",
       "integrity": "sha512-0FLj93y5USLHdnhIhABk83rm8XEGA7kH3cr+YUlvxoUGp1xNt/DINUMvqPxLyOQMzLmZe8i4RTHbvb8MC7NmrA==",
-      "dev": true,
+      "devOptional": true,
       "dependencies": {
         "@types/prop-types": "*",
         "@types/scheduler": "*",
@@ -4049,7 +4050,7 @@
       "version": "0.16.3",
       "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
       "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==",
-      "dev": true
+      "devOptional": true
     },
     "node_modules/@types/semver": {
       "version": "7.3.13",
@@ -6530,7 +6531,7 @@
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
       "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==",
-      "dev": true
+      "devOptional": true
     },
     "node_modules/dagre": {
       "version": "0.8.5",
@@ -9388,7 +9389,7 @@
       "version": "9.0.21",
       "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
       "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==",
-      "dev": true,
+      "devOptional": true,
       "funding": {
         "type": "opencollective",
         "url": "https://opencollective.com/immer"
@@ -12777,9 +12778,9 @@
       }
     },
     "node_modules/monaco-editor": {
-      "version": "0.38.0",
-      "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.38.0.tgz",
-      "integrity": "sha512-11Fkh6yzEmwx7O0YoLxeae0qEGFwmyPRlVxpg7oF9czOOCB/iCjdJrG5I67da5WiXK3YJCxoz9TJFE8Tfq/v9A=="
+      "version": "0.41.0",
+      "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.41.0.tgz",
+      "integrity": "sha512-1o4olnZJsiLmv5pwLEAmzHTE/5geLKQ07BrGxlF4Ri/AXAc2yyDGZwHjiTqD8D/ROKUZmwMA28A+yEowLNOEcA=="
     },
     "node_modules/ms": {
       "version": "2.1.2",
@@ -17384,6 +17385,14 @@
         "requires-port": "^1.0.0"
       }
     },
+    "node_modules/use-sync-external-store": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
+      "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
+      "peerDependencies": {
+        "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+      }
+    },
     "node_modules/util-deprecate": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -18430,6 +18439,33 @@
       "funding": {
         "url": "https://github.com/sponsors/sindresorhus"
       }
+    },
+    "node_modules/zustand": {
+      "version": "4.4.1",
+      "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.4.1.tgz",
+      "integrity": "sha512-QCPfstAS4EBiTQzlaGP1gmorkh/UL1Leaj2tdj+zZCZ/9bm0WS7sI2wnfD5lpOszFqWJ1DcPnGoY8RDL61uokw==",
+      "dependencies": {
+        "use-sync-external-store": "1.2.0"
+      },
+      "engines": {
+        "node": ">=12.7.0"
+      },
+      "peerDependencies": {
+        "@types/react": ">=16.8",
+        "immer": ">=9.0",
+        "react": ">=16.8"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        },
+        "immer": {
+          "optional": true
+        },
+        "react": {
+          "optional": true
+        }
+      }
     }
   }
 }
diff --git a/karavan-designer/package.json b/karavan-designer/package.json
index 548304dd..a89a6b7e 100644
--- a/karavan-designer/package.json
+++ b/karavan-designer/package.json
@@ -41,7 +41,8 @@
     "react": "18.2.0",
     "react-dom": "18.2.0",
     "rxjs": "7.8.1",
-    "uuid": "9.0.0"
+    "uuid": "9.0.0",
+    "zustand": "^4.4.1"
   },
   "devDependencies": {
     "@svgr/webpack": "^7.0.0",
diff --git a/karavan-designer/public/example/demo.camel.yaml b/karavan-designer/public/example/demo.camel.yaml
new file mode 100644
index 00000000..2db92f8d
--- /dev/null
+++ b/karavan-designer/public/example/demo.camel.yaml
@@ -0,0 +1,41 @@
+- route:
+    id: route-f435
+    from:
+      uri: kamelet:timer-source
+      id: from-e52c
+      steps:
+        - choice:
+            when:
+              - expression: {}
+                id: when-064f
+                steps:
+                  - multicast:
+                      id: multicast-38ce
+                      steps:
+                        - bean:
+                            id: bean-3b8e
+                        - log:
+                            message: ${body}
+                            id: log-546f
+                        - loop:
+                            expression: {}
+                            id: loop-4635
+                            steps:
+                              - convertBodyTo:
+                                  id: convertBodyTo-1cae
+            otherwise:
+              id: otherwise-0b09
+              steps:
+                - filter:
+                    expression: {}
+                    id: filter-a02b
+            id: choice-c53c
+        - doTry:
+            id: doTry-8fd5
+            doCatch:
+              - id: doCatch-1071
+              - id: doCatch-c38e
+            steps:
+              - routingSlip:
+                  expression: {}
+                  id: routingSlip-a85a
diff --git a/karavan-designer/src/App.tsx b/karavan-designer/src/App.tsx
index b9cae192..3e98dc50 100644
--- a/karavan-designer/src/App.tsx
+++ b/karavan-designer/src/App.tsx
@@ -94,7 +94,8 @@ class App extends React.Component<Props, State> {
             fetch("kamelets/kamelets.yaml"),
             fetch("components/components.json"),
             fetch("snippets/org.apache.camel.AggregationStrategy"),
-            fetch("snippets/org.apache.camel.Processor")
+            fetch("snippets/org.apache.camel.Processor"),
+            fetch("example/demo.camel.yaml")
             // fetch("components/supported-components.json"),
         ]).then(responses =>
             Promise.all(responses.map(response => response.text()))
@@ -113,6 +114,10 @@ class App extends React.Component<Props, State> {
             TemplateApi.saveTemplate("org.apache.camel.Processor", data[3]);
 
             if (data[4]) {
+                this.setState({yaml: data[4], name: "demo.camel.yaml"})
+            }
+
+            if (data[5]) {
                 ComponentApi.saveSupportedComponents(data[4]);
                 ComponentApi.setSupportedOnly(true);
             }
diff --git a/karavan-designer/src/designer/KaravanDesigner.tsx b/karavan-designer/src/designer/KaravanDesigner.tsx
index a2556b2a..b65a66f1 100644
--- a/karavan-designer/src/designer/KaravanDesigner.tsx
+++ b/karavan-designer/src/designer/KaravanDesigner.tsx
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React from 'react';
+import React, {useEffect, useState} from 'react';
 import {
     Badge,
     PageSection, PageSectionVariants, Tab, Tabs, TabTitleIcon, TabTitleText, Tooltip,
@@ -27,6 +27,8 @@ import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
 import {CamelUi} from "./utils/CamelUi";
 import {BeansDesigner} from "./beans/BeansDesigner";
 import {RestDesigner} from "./rest/RestDesigner";
+import {useDesignerStore, useIntegrationStore} from "./KaravanStore";
+import {shallow} from "zustand/shallow";
 import {getDesignerIcon} from "./utils/KaravanIcons";
 
 interface Props {
@@ -39,67 +41,70 @@ interface Props {
     tab?: string
 }
 
-interface State {
-    tab: string
-    integration: Integration
-    key: string
-    propertyOnly: boolean
-}
-
 export class KaravanInstance {
-    static designer: KaravanDesigner;
+    // static designer: KaravanDesigner;
 
-    static set(designer: KaravanDesigner): void  {
-        KaravanInstance.designer = designer;
-    }
-
-    static get(): KaravanDesigner {
-        return KaravanInstance.designer;
-    }
+    // static set(designer: KaravanDesigner): void  {
+    //     KaravanInstance.designer = designer;
+    // }
+    //
+    // static get(): KaravanDesigner {
+    //     return KaravanInstance.designer;
+    // }
 
     static getProps(): Props {
-        return KaravanInstance.designer?.props;
+        return {
+            dark: false, filename: "", onGetCustomCode(name: string, javaType: string): Promise<string | undefined> {
+                return Promise.resolve(undefined);
+            }, onSave(filename: string, yaml: string, propertyOnly: boolean): void {
+            }, onSaveCustomCode(name: string, code: string): void {
+            }, yaml: ""
+        };
     }
 }
 
-export class KaravanDesigner extends React.Component<Props, State> {
+export const KaravanDesigner = (props: Props) => {
 
-    getIntegration = (yaml: string, filename: string): Integration => {
+    const [tab, setTab] = useState<string>('routes');
+    const [propertyOnly, setPropertyOnly] = useDesignerStore((state) =>
+        [state.propertyOnly, state.setPropertyOnly], shallow )
+    const [integration, setIntegration] = useIntegrationStore((state) => 
+        [state.integration, state.setIntegration], shallow )
+
+    useEffect(() => {
+        console.log("useEffect");
+        setIntegration(makeIntegration(props.yaml, props.filename));
+    }, []);
+
+    function makeIntegration  (yaml: string, filename: string): Integration {
        if (yaml && CamelDefinitionYaml.yamlIsIntegration(yaml)) {
-           return CamelDefinitionYaml.yamlToIntegration(this.props.filename, this.props.yaml)
+           return CamelDefinitionYaml.yamlToIntegration(props.filename, props.yaml)
        } else {
            return Integration.createNew(filename, 'plain');
        }
     }
 
-    public state: State = {
-        tab: this.props.tab ? this.props.tab : 'routes',
-        integration: this.getIntegration(this.props.yaml, this.props.filename),
-        key: "",
-        propertyOnly: false,
-    }
-
-    componentDidMount() {
-        KaravanInstance.set(this);
-    }
-
-    componentDidUpdate = (prevProps: Readonly<Props>, prevState: Readonly<State>, snapshot?: any) => {
-        if (prevState.key !== this.state.key) {
-            this.props.onSave?.call(this, this.props.filename, this.getCode(this.state.integration), this.state.propertyOnly);
-        }
-    }
-
-    save = (integration: Integration, propertyOnly: boolean): void => {
-        this.setState({key: Math.random().toString(), integration: integration, propertyOnly: propertyOnly});
-    }
-
-    getCode = (integration: Integration): string => {
+    // componentDidMount() {
+    //     KaravanInstance.set(this);
+    // }
+    //
+    // componentDidUpdate = (prevProps: Readonly<Props>, prevState: Readonly<State>, snapshot?: any) => {
+    //     if (prevState.key !== key) {
+    //         props.onSave?.call(this, props.filename, this.getCode(integration), propertyOnly);
+    //     }
+    // }
+
+    // save = (integration: Integration, propertyOnly: boolean): void => {
+    //     this.setState({key: Math.random().toString(), integration: integration, propertyOnly: propertyOnly});
+    // }
+
+    function getCode (integration: Integration): string {
         const clone = CamelUtil.cloneIntegration(integration);
         return CamelDefinitionYaml.integrationToYaml(clone);
     }
 
-    getTab(title: string, tooltip: string, icon: string) {
-        const counts = CamelUi.getFlowCounts(this.state.integration);
+    function  getTab(title: string, tooltip: string, icon: string) {
+        const counts = CamelUi.getFlowCounts(integration);
         const count = counts.has(icon) && counts.get(icon) ? counts.get(icon) : undefined;
         const showCount = count && count > 0;
         return (
@@ -115,25 +120,23 @@ export class KaravanDesigner extends React.Component<Props, State> {
         )
     }
 
-    render() {
-        const tab = this.state.tab;
-        return (
-            <PageSection variant={this.props.dark ? PageSectionVariants.darker : PageSectionVariants.light} className="page" isFilled padding={{default: 'noPadding'}}>
-                <Tabs className="main-tabs" activeKey={tab} onSelect={(event, tabIndex) => this.setState({tab: tabIndex.toString()})} style={{width: "100%"}}>
-                    <Tab eventKey='routes' title={this.getTab("Routes", "Integration flows", "routes")}></Tab>
-                    <Tab eventKey='rest' title={this.getTab("REST", "REST services", "rest")}></Tab>
-                    <Tab eventKey='beans' title={this.getTab("Beans", "Beans Configuration", "beans")}></Tab>
-                </Tabs>
-                    {tab === 'routes' && <RouteDesigner integration={this.state.integration}
-                                                        onSave={(integration, propertyOnly) => this.save(integration, propertyOnly)}
-                                                        dark={this.props.dark}/>}
-                    {tab === 'rest' && <RestDesigner integration={this.state.integration}
-                                                     onSave={(integration, propertyOnly) => this.save(integration, propertyOnly)}
-                                                     dark={this.props.dark}/>}
-                    {tab === 'beans' && <BeansDesigner integration={this.state.integration}
-                                                       onSave={(integration, propertyOnly) => this.save(integration, propertyOnly)}
-                                                       dark={this.props.dark}/>}
-            </PageSection>
-        )
-    }
+    return (
+        <PageSection variant={props.dark ? PageSectionVariants.darker : PageSectionVariants.light} className="page" isFilled padding={{default: 'noPadding'}}>
+            <Tabs className="main-tabs" activeKey={tab} onSelect={(event, tabIndex) => setTab(tabIndex.toString())} style={{width: "100%"}}>
+                <Tab eventKey='routes' title={getTab("Routes", "Integration flows", "routes")}></Tab>
+                <Tab eventKey='rest' title={getTab("REST", "REST services", "rest")}></Tab>
+                <Tab eventKey='beans' title={getTab("Beans", "Beans Configuration", "beans")}></Tab>
+            </Tabs>
+            {tab === 'routes' && <RouteDesigner
+                // integration={integration}
+                                                // onSave={(integration, propertyOnly) => save(integration, propertyOnly)}
+                                                dark={props.dark}/>}
+            {/*{tab === 'rest' && <RestDesigner integration={integration}*/}
+            {/*                                 onSave={(integration, propertyOnly) => save(integration, propertyOnly)}*/}
+            {/*                                 dark={props.dark}/>}*/}
+            {/*{tab === 'beans' && <BeansDesigner integration={integration}*/}
+            {/*                                   onSave={(integration, propertyOnly) => save(integration, propertyOnly)}*/}
+            {/*                                   dark={props.dark}/>}*/}
+        </PageSection>
+    )
 }
\ No newline at end of file
diff --git a/karavan-designer/src/designer/KaravanStore.ts b/karavan-designer/src/designer/KaravanStore.ts
new file mode 100644
index 00000000..4e710e61
--- /dev/null
+++ b/karavan-designer/src/designer/KaravanStore.ts
@@ -0,0 +1,127 @@
+/*
+ * 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 {create} from 'zustand'
+import {CamelElement, Integration} from "karavan-core/lib/model/IntegrationDefinition";
+import {createWithEqualityFn} from "zustand/traditional";
+import {shallow} from "zustand/shallow";
+
+interface IntegrationState {
+    integration: Integration;
+    setIntegration: (integration: Integration) => void;
+}
+
+export const useIntegrationStore = createWithEqualityFn<IntegrationState>((set) => ({
+    integration: Integration.createNew("demo", "plain"),
+    setIntegration: (integration: Integration) => {
+        set({integration: integration})
+    },
+}), shallow)
+
+interface DesignerState {
+    shiftKeyPressed: boolean;
+    showSelector: boolean;
+    setShowSelector: (showSelector: boolean) => void;
+    showDeleteConfirmation: boolean;
+    setShowDeleteConfirmation: (showDeleteConfirmation: boolean) => void;
+    showMoveConfirmation: boolean;
+    setShowMoveConfirmation: (showMoveConfirmation: boolean) => void;
+    propertyOnly: boolean;
+    setPropertyOnly: (propertyOnly: boolean) => void;
+    showSteps: boolean;
+    setShowSteps: (showSteps: boolean) => void;
+    deleteMessage: string;
+    setDeleteMessage: (deleteMessage: string) => void;
+    parentDsl?: string;
+    setParentDsl: (parentDsl?: string) => void;
+    selectedPosition?: number;
+    setSelectedPosition: (selectedPosition?: number) => void;
+    selectedStep?: CamelElement;
+    setSelectedStep: (selectedStep?: CamelElement) => void;
+    parentId: string;
+    setParentId: (parentId: string) => void;
+    selectedUuids: string[];
+    setSelectedUuids: (selectedUuids: string[]) => void;
+    clipboardSteps: string[];
+    setClipboardSteps: (clipboardSteps: string[]) => void;
+    selectorTabIndex?: string | number
+    setSelectorTabIndex: (selectorTabIndex?: string | number) => void;
+    width: number,
+    height: number,
+    top: number,
+    left: number,
+    setPosition: (width: number, height: number, top: number, left: number) => void;
+}
+
+export const useDesignerStore = createWithEqualityFn<DesignerState>((set) => ({
+    shiftKeyPressed: false,
+    showSelector: false,
+    showDeleteConfirmation: false,
+    showMoveConfirmation: false,
+    deleteMessage: '',
+    parentId: '',
+    showSteps: true,
+    selectedUuids: [],
+    clipboardSteps: [],
+    propertyOnly: false,
+    setSelectorTabIndex: (selectorTabIndex?: string | number) => {
+        set({selectorTabIndex: selectorTabIndex})
+    },
+    setSelectedStep: (selectedStep?: CamelElement) => {
+        set({selectedStep: selectedStep})
+    },
+    setSelectedPosition: (selectedPosition?: number) => {
+        set({selectedPosition: selectedPosition})
+    },
+    setParentDsl: (parentDsl?: string) => {
+        set({parentDsl: parentDsl})
+    },
+    setShowSelector: (showSelector: boolean) => {
+        set({showSelector: showSelector})
+    },
+    setShowDeleteConfirmation: (showDeleteConfirmation: boolean) => {
+        set({showDeleteConfirmation: showDeleteConfirmation})
+    },
+    setShowMoveConfirmation: (showMoveConfirmation: boolean) => {
+        set({showMoveConfirmation: showMoveConfirmation})
+    },
+    setPropertyOnly: (propertyOnly: boolean) => {
+        set({propertyOnly: propertyOnly})
+    },
+    setShowSteps: (showSteps: boolean) => {
+        set({showSteps: showSteps})
+    },
+    setDeleteMessage: (deleteMessage: string) => {
+        set({deleteMessage: deleteMessage})
+    },
+    setParentId: (parentId: string) => {
+        set({parentId: parentId})
+    },
+    setSelectedUuids: (selectedUuids: string[]) => {
+        set({selectedUuids: selectedUuids})
+    },
+    setClipboardSteps: (clipboardSteps: string[]) => {
+        set({clipboardSteps: clipboardSteps})
+    },
+    width: 1000,
+    height: 1000,
+    top: 0,
+    left: 0,
+    setPosition: (width: number, height: number, top: number, left: number) => {
+        set({width: width, height: height, top: top, left: left})
+    },
+}), shallow)
\ No newline at end of file
diff --git a/karavan-designer/src/designer/route/DslConnections.tsx b/karavan-designer/src/designer/route/DslConnections.tsx
index 938042d4..db7cd2dd 100644
--- a/karavan-designer/src/designer/route/DslConnections.tsx
+++ b/karavan-designer/src/designer/route/DslConnections.tsx
@@ -14,67 +14,61 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React from 'react';
+import React, {useEffect, useState} from 'react';
 import '../karavan.css';
-import {Integration, CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
+import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
 import {DslPosition, EventBus} from "../utils/EventBus";
 import {CamelUi} from "../utils/CamelUi";
 import {Subscription} from "rxjs";
 import {SagaDefinition} from "karavan-core/lib/model/CamelDefinition";
+import {useDesignerStore} from "../KaravanStore";
 
-interface Props {
-    integration: Integration
-    width: number
-    height: number
-    top: number
-    left: number
-}
-
-interface State {
-    integration: Integration
-    steps: Map<string, DslPosition>
-}
 
 const overlapGap: number = 40;
 const outgoingDefinitions: string[] = ['ToDefinition', 'KameletDefinition', 'ToDynamicDefinition', "PollEnrichDefinition", "EnrichDefinition", "WireTapDefinition", "SagaDefinition"];
 
+export const DslConnections = () => {
 
-export class DslConnections extends React.Component<Props, State> {
-
-    public state: State = {
-        integration: this.props.integration,
-        steps: new Map<string, DslPosition>(),
-    };
-    sub?: Subscription;
+    const [ width, height, top, left] = useDesignerStore((s) =>
+        [s.width, s.height, s.top, s.left])
+    const [steps, setSteps] = useState<Map<string, DslPosition>>(new Map<string, DslPosition>());
 
-    componentDidMount() {
-        this.sub = EventBus.onPosition()?.subscribe((evt: DslPosition) => this.setPosition(evt));
-    }
-
-    componentWillUnmount() {
-        this.sub?.unsubscribe();
-    }
+    useEffect(() => {
+        console.log("DslConnections Start", width, height, top, left);
+        const sub = EventBus.onPosition()?.subscribe((evt: DslPosition) => setPosition(evt));
+        return () => {
+            console.log("DslConnections Stop");
+            sub?.unsubscribe();
+        };
+    }, [width, height, top, left]);
 
-    setPosition(evt: DslPosition) {
+    function setPosition(evt: DslPosition) {
         if (evt.command === "add") {
-            this.setState(prevState => ({steps: prevState.steps.set(evt.step.uuid, evt)}));
+            setSteps(prevSteps => {
+                prevSteps.set(evt.step.uuid, evt);
+                return prevSteps;
+            })
+        }
+        else if (evt.command === "delete") {
+            setSteps(prevSteps => {
+                prevSteps.clear();
+                Array.from(prevSteps.entries())
+                    .filter(value => value[1]?.parent?.uuid !== evt.step.uuid)
+                    .forEach(value => prevSteps.set(value[0], value[1]));
+                prevSteps.delete(evt.step.uuid);
+                return prevSteps;
+            })
+        }
+        else if (evt.command === "clean") {
+            setSteps(prevSteps => {
+                prevSteps.clear();
+                return prevSteps;
+            })
         }
-        else if (evt.command === "delete") this.setState(prevState => {
-            prevState.steps.clear();
-            Array.from(prevState.steps.entries())
-                .filter(value => value[1]?.parent?.uuid !== evt.step.uuid)
-                .forEach(value => prevState.steps.set(value[0], value[1]));
-            prevState.steps.delete(evt.step.uuid);
-            return {steps: prevState.steps};
-        });
-        else if (evt.command === "clean") this.setState(prevState => {
-            prevState.steps.clear();
-            return {steps: prevState.steps};
-        });
     }
 
-    getIncomings() {
-        let outs: [string, number][] = Array.from(this.state.steps.values())
+    function getIncomings() {
+        let outs: [string, number][] = Array.from(steps.values())
             .filter(pos => ["FromDefinition"].includes(pos.step.dslName))
             .filter(pos => !CamelUi.isElementInternalComponent(pos.step))
             .filter(pos => !(pos.step.dslName === 'FromDefinition' && CamelUi.hasInternalUri(pos.step)))
@@ -84,17 +78,17 @@ export class DslConnections extends React.Component<Props, State> {
                 return y1 > y2 ? 1 : -1
             })
             .map(pos => [pos.step.uuid, pos.headerRect.y]);
-        while (this.hasOverlap(outs)) {
-            outs = this.addGap(outs);
+        while (hasOverlap(outs)) {
+            outs = addGap(outs);
         }
         return outs;
     }
 
-    getIncoming(data: [string, number]) {
-        const pos = this.state.steps.get(data[0]);
+    function getIncoming(data: [string, number]) {
+        const pos = steps.get(data[0]);
         if (pos) {
-            const fromX = pos.headerRect.x + pos.headerRect.width / 2 - this.props.left;
-            const fromY = pos.headerRect.y + pos.headerRect.height / 2 - this.props.top;
+            const fromX = pos.headerRect.x + pos.headerRect.width / 2 - left;
+            const fromY = pos.headerRect.y + pos.headerRect.height / 2 - top;
             const r = pos.headerRect.height / 2;
 
             const incomingX = 20;
@@ -117,10 +111,10 @@ export class DslConnections extends React.Component<Props, State> {
         }
     }
 
-    getIncomingIcons(data: [string, number]) {
-        const pos = this.state.steps.get(data[0]);
+    function getIncomingIcons(data: [string, number]) {
+        const pos = steps.get(data[0]);
         if (pos) {
-            const fromY = pos.headerRect.y + pos.headerRect.height / 2 - this.props.top;
+            const fromY = pos.headerRect.y + pos.headerRect.height / 2 - top;
             const r = pos.headerRect.height / 2;
             const incomingX = 20;
             const imageX = incomingX - r + 5;
@@ -133,7 +127,7 @@ export class DslConnections extends React.Component<Props, State> {
         }
     }
 
-    hasOverlap(data: [string, number][]): boolean {
+    function hasOverlap(data: [string, number][]): boolean {
         let result = false;
         data.forEach((d, i, arr) => {
             if (i > 0 && d[1] - arr[i - 1][1] < overlapGap) result = true;
@@ -141,7 +135,7 @@ export class DslConnections extends React.Component<Props, State> {
         return result;
     }
 
-    addGap(data: [string, number][]): [string, number][] {
+    function addGap(data: [string, number][]): [string, number][] {
         const result: [string, number][] = [];
         data.forEach((d, i, arr) => {
             if (i > 0 && d[1] - arr[i - 1][1] < overlapGap) result.push([d[0], d[1] + overlapGap])
@@ -151,8 +145,8 @@ export class DslConnections extends React.Component<Props, State> {
     }
 
 
-    getOutgoings(): [string, number][] {
-        let outs: [string, number][] = Array.from(this.state.steps.values())
+    function getOutgoings(): [string, number][] {
+        let outs: [string, number][] = Array.from(steps.values())
             .filter(pos => outgoingDefinitions.includes(pos.step.dslName))
             .filter(pos => pos.step.dslName !== 'KameletDefinition' || (pos.step.dslName === 'KameletDefinition' && !CamelUi.isActionKamelet(pos.step)))
             .filter(pos => pos.step.dslName === 'ToDefinition' && !CamelUi.isActionKamelet(pos.step) && !CamelUi.isElementInternalComponent(pos.step))
@@ -163,21 +157,21 @@ export class DslConnections extends React.Component<Props, State> {
                 const y2 = pos2.headerRect.y + pos2.headerRect.height / 2;
                 return y1 > y2 ? 1 : -1
             })
-            .map(pos => [pos.step.uuid, pos.headerRect.y - this.props.top]);
-        while (this.hasOverlap(outs)) {
-            outs = this.addGap(outs);
+            .map(pos => [pos.step.uuid, pos.headerRect.y - top]);
+        while (hasOverlap(outs)) {
+            outs = addGap(outs);
         }
         return outs;
     }
 
-    getOutgoing(data: [string, number]) {
-        const pos = this.state.steps.get(data[0]);
+    function getOutgoing(data: [string, number]) {
+        const pos = steps.get(data[0]);
         if (pos) {
-            const fromX = pos.headerRect.x + pos.headerRect.width / 2 - this.props.left;
-            const fromY = pos.headerRect.y + pos.headerRect.height / 2 - this.props.top;
+            const fromX = pos.headerRect.x + pos.headerRect.width / 2 - left;
+            const fromY = pos.headerRect.y + pos.headerRect.height / 2 - top;
             const r = pos.headerRect.height / 2;
 
-            const outgoingX = this.props.width - 20;
+            const outgoingX = width - 20;
             const outgoingY = data[1] + 15;
 
             const lineX1 = fromX + r;
@@ -203,11 +197,11 @@ export class DslConnections extends React.Component<Props, State> {
         }
     }
 
-    getOutgoingIcons(data: [string, number]) {
-        const pos = this.state.steps.get(data[0]);
+    function getOutgoingIcons(data: [string, number]) {
+        const pos = steps.get(data[0]);
         if (pos) {
             const r = pos.headerRect.height / 2;
-            const outgoingX = this.props.width - 20;
+            const outgoingX = width - 20;
             const outgoingY = data[1] + 15;
             const imageX = outgoingX - r + 5;
             const imageY = outgoingY - r + 5;
@@ -219,55 +213,55 @@ export class DslConnections extends React.Component<Props, State> {
         }
     }
 
-    getInternals(): [string, number, boolean][] {
-        let outs: [string, number, boolean][] = Array.from(this.state.steps.values())
+    function getInternals(): [string, number, boolean][] {
+        let outs: [string, number, boolean][] = Array.from(steps.values())
             .filter(pos => outgoingDefinitions.includes(pos.step.dslName) && CamelUi.hasInternalUri(pos.step))
             .sort((pos1: DslPosition, pos2: DslPosition) => {
                 const y1 = pos1.headerRect.y + pos1.headerRect.height / 2;
                 const y2 = pos2.headerRect.y + pos2.headerRect.height / 2;
                 return y1 > y2 ? 1 : -1
             })
-            .map(pos => [pos.step.uuid, pos.headerRect.y - this.props.top, pos.isSelected]);
+            .map(pos => [pos.step.uuid, pos.headerRect.y - top, pos.isSelected]);
         return outs;
     }
 
-    getInternalLines(data: [string, number, boolean]) {
-        const pos = this.state.steps.get(data[0]);
+    function getInternalLines(data: [string, number, boolean]) {
+        const pos = steps.get(data[0]);
         const uri = (pos?.step as any).uri;
         if (uri && uri.length && pos) {
             const key = pos.step.uuid + "-outgoing"
-            const fromX = pos.headerRect.x + pos.headerRect.width / 2 - this.props.left;
-            const fromY = pos.headerRect.y + pos.headerRect.height / 2 - this.props.top;
+            const fromX = pos.headerRect.x + pos.headerRect.width / 2 - left;
+            const fromY = pos.headerRect.y + pos.headerRect.height / 2 - top;
             const r = pos.headerRect.height / 2;
             const className = (CamelUi.hasDirectUri(pos.step) ? "path-direct" : "path-seda") + (data[2] ? "-selected" : "");
-            return this.getInternalLine(uri, key, className, fromX, fromY, r, data[1]);
+            return getInternalLine(uri, key, className, fromX, fromY, r, data[1]);
         } else if (pos?.step.dslName === 'SagaDefinition'){
             const saga = (pos?.step as SagaDefinition);
-            const fromX = pos.headerRect.x + pos.headerRect.width / 2 - this.props.left;
-            const fromY = pos.headerRect.y + pos.headerRect.height / 2 - this.props.top;
+            const fromX = pos.headerRect.x + pos.headerRect.width / 2 - left;
+            const fromY = pos.headerRect.y + pos.headerRect.height / 2 - top;
             const r = pos.headerRect.height / 2;
             const result:any[] = [];
             if (saga.completion && (saga.completion.startsWith("direct") || saga.completion.startsWith("seda"))){
                 const key = pos.step.uuid + "-completion"
                 const className = saga.completion.startsWith("direct") ? "path-direct" : "path-seda";
-                result.push(this.getInternalLine(saga.completion, key, className, fromX, fromY, r, data[1]));
+                result.push(getInternalLine(saga.completion, key, className, fromX, fromY, r, data[1]));
             }
             if (saga.compensation && (saga.compensation.startsWith("direct") || saga.compensation.startsWith("seda"))){
                 const key = pos.step.uuid + "-compensation"
                 const className = saga.compensation.startsWith("direct") ? "path-direct" : "path-seda";
-                result.push(this.getInternalLine(saga.compensation, key, className, fromX, fromY, r, data[1]));
+                result.push(getInternalLine(saga.compensation, key, className, fromX, fromY, r, data[1]));
             }
             return result;
         }
     }
 
-    getInternalLine(uri: string, key: string, className: string, fromX: number, fromY: number, r: number, i: number) {
-        const target = Array.from(this.state.steps.values())
+    function getInternalLine(uri: string, key: string, className: string, fromX: number, fromY: number, r: number, i: number) {
+        const target = Array.from(steps.values())
             .filter(s => s.step.dslName === 'FromDefinition')
             .filter(s => (s.step as any).uri && (s.step as any).uri === uri)[0];
         if (target) {
-            const targetX = target.headerRect.x + target.headerRect.width / 2 - this.props.left;
-            const targetY = target.headerRect.y + target.headerRect.height / 2 - this.props.top;
+            const targetX = target.headerRect.x + target.headerRect.width / 2 - left;
+            const targetY = target.headerRect.y + target.headerRect.height / 2 - top;
             const gap = 100;
             const add = 0.2;
 
@@ -294,7 +288,7 @@ export class DslConnections extends React.Component<Props, State> {
                 const pointX4 = pointLX + coefX;
                 const pointY4 = endY;
 
-                return this.getInternalPath(key, className, startX, startY, pointX1, pointY1, pointX2, pointY2, pointLX, pointLY, pointX3, pointY3, pointX4, pointY4, endX, endY);
+                return getInternalPath(key, className, startX, startY, pointX1, pointY1, pointX2, pointY2, pointLX, pointLY, pointX3, pointY3, pointX4, pointY4, endX, endY);
             } else if (targetX > fromX && targetX - fromX < gap) {
                 const startX = fromX - r;
                 const startY = fromY;
@@ -317,7 +311,7 @@ export class DslConnections extends React.Component<Props, State> {
                 const pointX4 = pointLX - coefX/2;
                 const pointY4 = endY;
 
-                return this.getInternalPath(key, className, startX, startY, pointX1, pointY1, pointX2, pointY2, pointLX, pointLY, pointX3, pointY3, pointX4, pointY4, endX, endY);
+                return getInternalPath(key, className, startX, startY, pointX1, pointY1, pointX2, pointY2, pointLX, pointLY, pointX3, pointY3, pointX4, pointY4, endX, endY);
             } else if (targetX <= fromX && fromX - targetX < gap) {
                 const startX = fromX + r;
                 const startY = fromY;
@@ -340,7 +334,7 @@ export class DslConnections extends React.Component<Props, State> {
                 const pointX4 = pointLX - coefX/2;
                 const pointY4 = endY;
 
-                return this.getInternalPath(key, className, startX, startY, pointX1, pointY1, pointX2, pointY2, pointLX, pointLY, pointX3, pointY3, pointX4, pointY4, endX, endY);
+                return getInternalPath(key, className, startX, startY, pointX1, pointY1, pointX2, pointY2, pointLX, pointLY, pointX3, pointY3, pointX4, pointY4, endX, endY);
             } else {
                 const startX = fromX - r;
                 const startY = fromY;
@@ -363,12 +357,12 @@ export class DslConnections extends React.Component<Props, State> {
                 const pointX4 = pointLX + coefX;
                 const pointY4 = endY;
 
-                return this.getInternalPath(key, className, startX, startY, pointX1, pointY1, pointX2, pointY2, pointLX, pointLY, pointX3, pointY3, pointX4, pointY4, endX, endY);
+                return getInternalPath(key, className, startX, startY, pointX1, pointY1, pointX2, pointY2, pointLX, pointLY, pointX3, pointY3, pointX4, pointY4, endX, endY);
             }
         }
     }
 
-    getInternalPath(key: string, className: string, startX: number, startY: number, pointX1: number, pointY1: number, pointX2: number, pointY2: number, pointLX: number, pointLY: number,
+    function getInternalPath(key: string, className: string, startX: number, startY: number, pointX1: number, pointY1: number, pointX2: number, pointY2: number, pointLX: number, pointLY: number,
                     pointX3: number, pointY3: number, pointX4: number, pointY4: number, endX: number, endY: number) {
         return (
             <g key={key}>
@@ -380,35 +374,35 @@ export class DslConnections extends React.Component<Props, State> {
         )
     }
 
-    getCircle(pos: DslPosition) {
-        const cx = pos.headerRect.x + pos.headerRect.width / 2 - this.props.left;
-        const cy = pos.headerRect.y + pos.headerRect.height / 2 - this.props.top;
+    function getCircle(pos: DslPosition) {
+        const cx = pos.headerRect.x + pos.headerRect.width / 2 - left;
+        const cy = pos.headerRect.y + pos.headerRect.height / 2 - top;
         const r = pos.headerRect.height / 2;
         return (
             <circle cx={cx} cy={cy} r={r} stroke="transparent" strokeWidth="3" fill="transparent" key={pos.step.uuid + "-circle"}/>
         )
     }
 
-    hasSteps = (step: CamelElement): boolean => {
+    function hasSteps  (step: CamelElement): boolean  {
         return (step.hasSteps() && !['FromDefinition'].includes(step.dslName))
             || ['RouteDefinition', 'TryDefinition', 'ChoiceDefinition', 'SwitchDefinition'].includes(step.dslName);
     }
 
-    getPreviousStep(pos: DslPosition) {
-        return Array.from(this.state.steps.values())
+    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];
     }
 
-    getArrow(pos: DslPosition) {
-        const endX = pos.headerRect.x + pos.headerRect.width / 2 - this.props.left;
-        const endY = pos.headerRect.y - 9 - this.props.top;
+    function getArrow(pos: DslPosition) {
+        const endX = pos.headerRect.x + pos.headerRect.width / 2 - left;
+        const endY = pos.headerRect.y - 9 - top;
         if (pos.parent) {
-            const parent = this.state.steps.get(pos.parent.uuid);
+            const parent = steps.get(pos.parent.uuid);
             if (parent) {
-                const startX = parent.headerRect.x + parent.headerRect.width / 2 - this.props.left;
-                const startY = parent.headerRect.y + parent.headerRect.height - this.props.top;
+                const startX = parent.headerRect.x + parent.headerRect.width / 2 - left;
+                const startY = parent.headerRect.y + parent.headerRect.height - top;
                 if ((!pos.inSteps || (pos.inSteps && pos.position === 0)) && parent.step.dslName !== 'MulticastDefinition') {
                     return (
                         <path name={pos.step.dslName} d={`M ${startX},${startY} C ${startX},${endY} ${endX},${startY}   ${endX},${endY}`}
@@ -419,22 +413,22 @@ export class DslConnections extends React.Component<Props, State> {
                         <path d={`M ${startX},${startY} C ${startX},${endY} ${endX},${startY}   ${endX},${endY}`}
                               className="path" key={pos.step.uuid} markerEnd="url(#arrowhead)"/>
                     )
-                } else if (pos.inSteps && pos.position > 0 && !this.hasSteps(pos.step)) {
-                    const prev = this.getPreviousStep(pos);
+                } else if (pos.inSteps && pos.position > 0 && !hasSteps(pos.step)) {
+                    const prev = getPreviousStep(pos);
                     if (prev) {
-                        const r = this.hasSteps(prev.step) ? prev.rect : prev.headerRect;
-                        const prevX = r.x + r.width / 2 - this.props.left;
-                        const prevY = r.y + r.height - this.props.top;
+                        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 x1={prevX} y1={prevY} x2={endX} y2={endY} className="path" key={pos.step.uuid} markerEnd="url(#arrowhead)"/>
                         )
                     }
-                } else if (pos.inSteps && pos.position > 0 && this.hasSteps(pos.step)) {
-                    const prev = this.getPreviousStep(pos);
+                } else if (pos.inSteps && pos.position > 0 && hasSteps(pos.step)) {
+                    const prev = getPreviousStep(pos);
                     if (prev) {
-                        const r = this.hasSteps(prev.step) ? prev.rect : prev.headerRect;
-                        const prevX = r.x + r.width / 2 - this.props.left;
-                        const prevY = r.y + r.height - this.props.top;
+                        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 x1={prevX} y1={prevY} x2={endX} y2={endY} className="path" key={pos.step.uuid} markerEnd="url(#arrowhead)"/>
                         )
@@ -444,33 +438,31 @@ export class DslConnections extends React.Component<Props, State> {
         }
     }
 
-    getSvg() {
-        const steps = Array.from(this.state.steps.values());
+    function getSvg() {
+        const stepsArray = Array.from(steps.values());
         return (
             <svg
-                style={{width: this.props.width, height: this.props.height, position: "absolute", left: 0, top: 0}}
-                viewBox={"0 0 " + this.props.width + " " + this.props.height}>
+                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">
                         <polygon points="0 0, 9 3, 0 6"/>
                     </marker>
                 </defs>
-                {steps.map(pos => this.getCircle(pos))}
-                {steps.map(pos => this.getArrow(pos))}
-                {this.getIncomings().map(p => this.getIncoming(p))}
-                {this.getOutgoings().map(p => this.getOutgoing(p))}
-                {/*{this.getInternals().map((p) => this.getInternalLines(p)).flat()}*/}
+                {stepsArray.map(pos => getCircle(pos))}
+                {stepsArray.map(pos => getArrow(pos))}
+                {getIncomings().map(p => getIncoming(p))}
+                {getOutgoings().map(p => getOutgoing(p))}
+                {/*{getInternals().map((p) => getInternalLines(p)).flat()}*/}
             </svg>
         )
     }
 
-    render() {
-        return (
-            <div className="connections" style={{width: this.props.width, height: this.props.height, marginTop: "8px"}}>
-                {this.getSvg()}
-                {this.getIncomings().map(p => this.getIncomingIcons(p))}
-                {this.getOutgoings().map(p => this.getOutgoingIcons(p))}
-            </div>
-        );
-    }
+    return (
+        <div className="connections" style={{width: width, height: height, marginTop: "0px"}}>
+            {getSvg()}
+            {getIncomings().map(p => getIncomingIcons(p))}
+            {getOutgoings().map(p => getOutgoingIcons(p))}
+        </div>
+    )
 }
diff --git a/karavan-designer/src/designer/route/DslElement.tsx b/karavan-designer/src/designer/route/DslElement.tsx
index 2ea1aaa2..2b498d02 100644
--- a/karavan-designer/src/designer/route/DslElement.tsx
+++ b/karavan-designer/src/designer/route/DslElement.tsx
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React, {CSSProperties} from 'react';
+import React, {createRef, CSSProperties, useState} from 'react';
 import {
     Button,
     Flex,
@@ -27,145 +27,149 @@ import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-circle-icon"
 import InsertIcon from "@patternfly/react-icons/dist/js/icons/arrow-alt-circle-right-icon";
 import {CamelElement, Integration} from "karavan-core/lib/model/IntegrationDefinition";
 import {CamelUi} from "../utils/CamelUi";
-import {EventBus} from "../utils/EventBus";
+import {DslPosition, EventBus} from "../utils/EventBus";
 import {ChildElement, CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
 import ReactDOM from "react-dom";
 import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
 import {CamelDisplayUtil} from "karavan-core/lib/api/CamelDisplayUtil";
+import {useDesignerStore, useIntegrationStore} from "../KaravanStore";
+import {shallow} from "zustand/shallow";
+import {useRouteDesignerHook} from "./useRouteDesignerHook";
 
 interface Props {
-    integration: Integration,
+    // integration: Integration,
     step: CamelElement,
     parent: CamelElement | undefined,
-    deleteElement: any
-    selectElement: any
-    openSelector: (parentId: string | undefined, parentDsl: string | undefined, showSteps: boolean, position?: number | undefined) => void
-    moveElement: (source: string, target: string, asChild: boolean) => void
-    selectedUuid: string []
+    // deleteElement: any
+    // selectElement: any
+    // openSelector: (parentId: string | undefined, parentDsl: string | undefined, showSteps: boolean, position?: number | undefined) => void
+    // moveElement: (source: string, target: string, asChild: boolean) => void
+    // selectedUuid: string []
     inSteps: boolean
     position: number
 }
 
-interface State {
-    showSelector: boolean
-    showMoveConfirmation: boolean
-    moveElements: [string | undefined, string | undefined]
-    tabIndex: string | number
-    isDragging: boolean
-    isDraggedOver: boolean
-}
+export const DslElement = (props: Props) => {
+
+    const ref = React.createRef();
+    const { deleteElement, selectElement, moveElement, onShowDeleteConfirmation, openSelector } = useRouteDesignerHook();
 
-export class DslElement extends React.Component<Props, State> {
+    const [integration, setIntegration] = useIntegrationStore((s) => [s.integration, s.setIntegration], shallow)
 
-    public state: State = {
-        showSelector: false,
-        showMoveConfirmation: false,
-        moveElements: [undefined, undefined],
-        tabIndex: 0,
-        isDragging: false,
-        isDraggedOver: false,
-    };
+    const [showSelector, showDeleteConfirmation, propertyOnly, showSteps,deleteMessage,parentId, selectedUuids,clipboardSteps, parentDsl,selectedPosition,selectedStep,selectorTabIndex,showMoveConfirmation,
+        setShowSelector, setShowDeleteConfirmation,setPropertyOnly,setShowSteps,setDeleteMessage, setParentId, setParentDsl, setSelectedPosition, setSelectedUuids, setClipboardSteps,setPosition,setShowMoveConfirmation,
+        width, height, top, left] = useDesignerStore((s) =>
+        [s.showSelector, s.showDeleteConfirmation, s.propertyOnly, s.showSteps,s.deleteMessage,s.parentId, s.selectedUuids,s.clipboardSteps, s.parentDsl, s.selectedPosition, s.selectedStep,s.selectorTabIndex, s.showMoveConfirmation,
+            s.setShowSelector, s.setShowDeleteConfirmation,s.setPropertyOnly,s.setShowSteps,s.setDeleteMessage, s.setParentId, s.setParentDsl, s.setSelectedPosition, s.setSelectedUuids, s.setClipboardSteps, s.setPosition, s.setShowMoveConfirmation,
+            s.width, s.height, s.top, s.left], shallow)
+    const [isDragging, setIsDragging] = useState<boolean>(false);
+
+    const [isDraggedOver, setIsDraggedOver] = useState<boolean>(false);
+    const [moveElements, setMoveElements] = useState<[string | undefined, string | undefined]>([undefined, undefined]);
 
     //
     // componentDidUpdate = (prevProps: Readonly<Props>, prevState: Readonly<State>, snapshot?: any) => {
-    //     if (prevState.selectedUuid !== this.props.selectedUuid) {
-    //         this.setState({selectedUuid: this.props.selectedUuid});
+    //     if (prevselectedUuid !== props.selectedUuid) {
+    //         setState({selectedUuid: props.selectedUuid});
     //     }
     // }
 
-    openSelector = (evt: React.MouseEvent, showSteps: boolean = true, isInsert: boolean = false) => {
+    function onOpenSelector (evt: React.MouseEvent, showSteps: boolean = true, isInsert: boolean = false) {
         evt.stopPropagation();
-        if (isInsert && this.props.parent) {
-            this.props.openSelector.call(this, this.props.parent.uuid, this.props.parent.dslName, showSteps, this.props.position);
+        if (isInsert && props.parent) {
+            openSelector(props.parent.uuid, props.parent.dslName, showSteps, props.position);
         } else {
-            this.props.openSelector.call(this, this.props.step.uuid, this.props.step.dslName, showSteps);
+            openSelector(props.step.uuid, props.step.dslName, showSteps);
         }
     }
 
-    closeDslSelector = () => {
-        this.setState({showSelector: false})
+    function closeDslSelector () {
+        // setState({showSelector: false})
+        setShowSelector(false);
     }
 
-    delete = (evt: React.MouseEvent) => {
+    function onDeleteElement (evt: React.MouseEvent) {
         evt.stopPropagation();
-        this.props.deleteElement.call(this, this.props.step.uuid);
+        onShowDeleteConfirmation(props.step.uuid);
     }
 
-    selectElement = (evt: React.MouseEvent) => {
+    function onSelectElement (evt: React.MouseEvent) {
         evt.stopPropagation();
-        this.props.selectElement.call(this, this.props.step);
+        selectElement(props.step);
     }
 
-    dragElement = (event: React.DragEvent<HTMLDivElement>, element: CamelElement) => {
+    function dragElement (event: React.DragEvent<HTMLDivElement>, element: CamelElement) {
         event.preventDefault();
         event.stopPropagation();
-        this.setState({isDraggedOver: false});
+        setIsDraggedOver(false);
         const sourceUuid = event.dataTransfer.getData("text/plain");
         const targetUuid = element.uuid;
         if (sourceUuid !== targetUuid) {
             if (element.hasSteps()){
-                this.setState({showMoveConfirmation: true, moveElements: [sourceUuid, targetUuid]});
+                setShowMoveConfirmation(true);
+                setMoveElements([sourceUuid, targetUuid])
             } else {
-                this.props.moveElement?.call(this, sourceUuid, targetUuid, false);
+                moveElement(sourceUuid, targetUuid, false);
             }
         }
     }
 
-    confirmMove = (asChild: boolean) => {
-        const sourceUuid = this.state.moveElements[0];
-        const targetUuid = this.state.moveElements[1];
+    function confirmMove (asChild: boolean) {
+        const sourceUuid = moveElements[0];
+        const targetUuid = moveElements[1];
         if (sourceUuid && targetUuid && sourceUuid !== targetUuid) {
-            this.props.moveElement?.call(this, sourceUuid, targetUuid, asChild);
-            this.setState({showMoveConfirmation: false, moveElements: [undefined, undefined]})
+            moveElement(sourceUuid, targetUuid, asChild);
+            cancelMove();
         }
     }
 
-    cancelMove = () => {
-        this.setState({showMoveConfirmation: false, moveElements: [undefined, undefined]})
+    function cancelMove () {
+        setShowMoveConfirmation(false);
+        setMoveElements([undefined, undefined]);
     }
 
-    isSelected = (): boolean => {
-        return this.props.selectedUuid.includes(this.props.step.uuid);
+    function isSelected (): boolean {
+        return selectedUuids.includes(props.step.uuid);
     }
 
-    hasBorder = (): boolean => {
-        return (this.props.step?.hasSteps() && !['FromDefinition'].includes(this.props.step.dslName))
+    function hasBorder (): boolean {
+        return (props.step?.hasSteps() && !['FromDefinition'].includes(props.step.dslName))
             || ['RouteConfigurationDefinition',
                 'RouteDefinition',
                 'TryDefinition',
                 'ChoiceDefinition',
-                'SwitchDefinition'].includes(this.props.step.dslName);
+                'SwitchDefinition'].includes(props.step.dslName);
     }
 
-    isNotDraggable = (): boolean => {
-        return ['FromDefinition', 'RouteConfigurationDefinition', 'RouteDefinition', 'WhenDefinition', 'OtherwiseDefinition'].includes(this.props.step.dslName);
+    function isNotDraggable (): boolean {
+        return ['FromDefinition', 'RouteConfigurationDefinition', 'RouteDefinition', 'WhenDefinition', 'OtherwiseDefinition'].includes(props.step.dslName);
     }
 
-    isWide = (): boolean => {
+    function isWide (): boolean {
         return ['RouteConfigurationDefinition', 'RouteDefinition', 'ChoiceDefinition', 'SwitchDefinition', 'MulticastDefinition', 'TryDefinition', 'CircuitBreakerDefinition']
-            .includes(this.props.step.dslName);
+            .includes(props.step.dslName);
     }
 
-    isAddStepButtonLeft = (): boolean => {
+    function isAddStepButtonLeft (): boolean {
         return ['MulticastDefinition']
-            .includes(this.props.step.dslName);
+            .includes(props.step.dslName);
     }
 
-    isHorizontal = (): boolean => {
-        return ['MulticastDefinition'].includes(this.props.step.dslName);
+    function isHorizontal (): boolean {
+        return ['MulticastDefinition'].includes(props.step.dslName);
     }
 
-    isRoot = (): boolean => {
-        return ['RouteConfigurationDefinition', 'RouteDefinition'].includes(this.props.step?.dslName);
+    function isRoot (): boolean {
+        return ['RouteConfigurationDefinition', 'RouteDefinition'].includes(props.step?.dslName);
     }
 
-    isInStepWithChildren = () => {
-        const step: CamelElement = this.props.step;
+    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 && this.props.inSteps;
+        return children.filter((c: ChildElement) => c.name === 'steps' || c.multiple).length > 0 && props.inSteps;
     }
 
-    getChildrenInfo = (step: CamelElement): [boolean, number, boolean, number, number] => {
+    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
@@ -185,49 +189,50 @@ export class DslElement extends React.Component<Props, State> {
         return [hasStepsField, stepsChildrenCount, hasNonStepsFields, nonStepChildrenCount, childrenCount]
     }
 
-    hasWideChildrenElement = () => {
-        const [hasStepsField, stepsChildrenCount, hasNonStepsFields, nonStepChildrenCount, childrenCount] = this.getChildrenInfo(this.props.step);
-        if (this.isHorizontal() && stepsChildrenCount > 1) return true;
+    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;
     }
 
-    hasBorderOverSteps = (step: CamelElement) => {
-        const [hasStepsField, stepsChildrenCount, hasNonStepsFields, nonStepChildrenCount] = this.getChildrenInfo(step);
+    function hasBorderOverSteps  (step: CamelElement)  {
+        const [hasStepsField, stepsChildrenCount, hasNonStepsFields, nonStepChildrenCount] = getChildrenInfo(step);
         if (hasStepsField && stepsChildrenCount > 0 && hasNonStepsFields && nonStepChildrenCount > 0) return true;
         else return false;
     }
 
-    getHeaderStyle = () => {
+    function getHeaderStyle () {
         const style: CSSProperties = {
-            width: this.isWide() ? "100%" : "",
-            fontWeight: this.isSelected() ? "bold" : "normal",
+            width: isWide() ? "100%" : "",
+            fontWeight: isSelected() ? "bold" : "normal",
         };
         return style;
     }
 
-    sendPosition = (el: HTMLDivElement | null, isSelected: boolean) => {
-        const node = ReactDOM.findDOMNode(this);
+    function sendPosition (el: HTMLDivElement | null, isSelected: boolean) {
+        // console.log("sendPosition", props.step)
+        const node = el;
         if (node && el) {
             const header = Array.from(node.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 (this.props.step.show){
-                    EventBus.sendPosition("add", this.props.step, this.props.parent, rect, headerRect, this.props.position, this.props.inSteps, isSelected);
+                if (props.step.show){
+                    EventBus.sendPosition("add", props.step, props.parent, rect, headerRect, props.position, props.inSteps, isSelected);
                 } else {
-                    EventBus.sendPosition("delete", this.props.step, this.props.parent, new DOMRect(), new DOMRect(), 0);
+                    EventBus.sendPosition("delete", props.step, props.parent, new DOMRect(), new DOMRect(), 0);
                 }
             }
         }
     }
 
-    getHeader = () => {
-        const step: CamelElement = this.props.step;
-        const parent = this.props.parent;
+    function getHeader () {
+        const step: CamelElement = props.step;
+        const parent = props.parent;
         const inRouteConfiguration = parent !== undefined && parent.dslName === 'RouteConfigurationDefinition';
         const availableModels = CamelUi.getSelectorModelsForParent(step.dslName, false);
         const showAddButton = !['CatchDefinition', 'RouteDefinition'].includes(step.dslName) && availableModels.length > 0;
@@ -235,31 +240,31 @@ export class DslElement extends React.Component<Props, State> {
             !['FromDefinition', 'RouteConfigurationDefinition', 'RouteDefinition', 'CatchDefinition', 'FinallyDefinition', 'WhenDefinition', 'OtherwiseDefinition'].includes(step.dslName)
             && !inRouteConfiguration;
         const headerClass = ['RouteConfigurationDefinition', 'RouteDefinition'].includes(step.dslName) ? "header-route" : "header"
-        const headerClasses = this.isSelected() ? headerClass + " selected" : headerClass;
+        const headerClasses = isSelected() ? headerClass + " selected" : headerClass;
         return (
-            <div className={headerClasses} style={this.getHeaderStyle()}>
-                {!['RouteConfigurationDefinition', 'RouteDefinition'].includes(this.props.step.dslName) &&
-                    <div ref={el => this.sendPosition(el, this.isSelected())}
+            <div className={headerClasses} style={getHeaderStyle()}>
+                {!['RouteConfigurationDefinition', 'RouteDefinition'].includes(props.step.dslName) &&
+                    <div ref={el => sendPosition(el, isSelected())}
                          className={"header-icon"}
-                         style={this.isWide() ? {width: ""} : {}}>
+                         style={isWide() ? {width: ""} : {}}>
                         {CamelUi.getIconForElement(step)}
                     </div>
                 }
-                <div className={this.hasWideChildrenElement() ? "header-text" : ""}>
-                    {this.hasWideChildrenElement() && <div className="spacer"/>}
-                    {this.getHeaderTextWithTooltip(step)}
+                <div className={hasWideChildrenElement() ? "header-text" : ""}>
+                    {hasWideChildrenElement() && <div className="spacer"/>}
+                    {getHeaderTextWithTooltip(step)}
                 </div>
-                {showInsertButton && this.getInsertElementButton()}
-                {this.getDeleteButton()}
-                {showAddButton && this.getAddElementButton()}
+                {showInsertButton && getInsertElementButton()}
+                {getDeleteButton()}
+                {showAddButton && getAddElementButton()}
             </div>
         )
     }
 
-    getHeaderTextWithTooltip = (step: CamelElement) => {
+    function getHeaderTextWithTooltip (step: CamelElement) {
         const checkRequired = CamelUtil.checkRequired(step);
-        const title = (step as any).description ? (step as any).description : CamelUi.getElementTitle(this.props.step);
-        let className = this.hasWideChildrenElement() ? "text text-right" : "text text-bottom";
+        const title = (step as any).description ? (step as any).description : CamelUi.getElementTitle(props.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 (
@@ -270,30 +275,30 @@ export class DslElement extends React.Component<Props, State> {
         )
     }
 
-    getHeaderWithTooltip = (tooltip: string | undefined) => {
+    function getHeaderWithTooltip (tooltip: string | undefined) {
         return (
             <Tooltip position={"left"}
                      content={<div>{tooltip}</div>}>
-                {this.getHeader()}
+                {getHeader()}
             </Tooltip>
         )
     }
 
-    getHeaderTooltip = (): string | undefined => {
-        if (CamelUi.isShowExpressionTooltip(this.props.step)) return CamelUi.getExpressionTooltip(this.props.step);
-        if (CamelUi.isShowUriTooltip(this.props.step)) return CamelUi.getUriTooltip(this.props.step);
+    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;
     }
 
-    getElementHeader = () => {
-        const tooltip = this.getHeaderTooltip();
-        if (tooltip !== undefined && !this.state.isDragging) {
-            return this.getHeaderWithTooltip(tooltip);
+    function getElementHeader () {
+        const tooltip = getHeaderTooltip();
+        if (tooltip !== undefined && !isDragging) {
+            return getHeaderWithTooltip(tooltip);
         }
-        return this.getHeader();
+        return getHeader();
     }
 
-    getChildrenStyle = () => {
+    function getChildrenStyle ()  {
         const style: CSSProperties = {
             display: "flex",
             flexDirection: "row",
@@ -301,22 +306,22 @@ export class DslElement extends React.Component<Props, State> {
         return style;
     }
 
-    getChildrenElementsStyle = (child: ChildElement, notOnlySteps: boolean) => {
-        const step = this.props.step;
-        const isBorder = child.name === 'steps' && this.hasBorderOverSteps(step);
+    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: this.isHorizontal() || child.name !== 'steps' ? "flex" : "block",
+            display: isHorizontal() || child.name !== 'steps' ? "flex" : "block",
             flexDirection: "row",
         }
         return style;
     }
 
-    getChildElements = () => {
-        const step: CamelElement = this.props.step;
+    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;
@@ -331,67 +336,61 @@ export class DslElement extends React.Component<Props, State> {
             children = children.filter(value => value.name !== 'onWhen')
         }
         return (
-            <div key={step.uuid + "-children"} className="children" style={this.getChildrenStyle()}>
-                {children.map((child: ChildElement, index: number) => this.getChildDslElements(child, index, notOnlySteps))}
+            <div key={step.uuid + "-children"} className="children" style={getChildrenStyle()}>
+                {children.map((child: ChildElement, index: number) => getChildDslElements(child, index, notOnlySteps))}
             </div>
         )
     }
 
-    getChildDslElements = (child: ChildElement, index: number, notOnlySteps: boolean) => {
-        const step = this.props.step;
+    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={this.getChildrenElementsStyle(child, notOnlySteps)} key={step.uuid + "-child-" + index}>
+                <div className={child.name + " has-child"} style={getChildrenElementsStyle(child, notOnlySteps)} key={step.uuid + "-child-" + index}>
                     {children.map((element, index) => (
                         <div key={step.uuid + child.className + index}>
                             <DslElement
-                                integration={this.props.integration}
-                                openSelector={this.props.openSelector}
-                                deleteElement={this.props.deleteElement}
-                                selectElement={this.props.selectElement}
-                                moveElement={this.props.moveElement}
-                                selectedUuid={this.props.selectedUuid}
                                 inSteps={child.name === 'steps'}
                                 position={index}
                                 step={element}
                                 parent={step}/>
                         </div>
                     ))}
-                    {child.name === 'steps' && this.getAddStepButton()}
+                    {child.name === 'steps' && getAddStepButton()}
                 </div>
             )
         } else if (child.name === 'steps') {
             return (
-                <div className={child.name + " has-child"} style={this.getChildrenElementsStyle(child, notOnlySteps)} key={step.uuid + "-child-" + index}>
-                    {this.getAddStepButton()}
+                <div className={child.name + " has-child"} style={getChildrenElementsStyle(child, notOnlySteps)} key={step.uuid + "-child-" + index}>
+                    {getAddStepButton()}
                 </div>
             )
         }
     }
 
-    getAddStepButton() {
-        const {integration, step, selectedUuid} = this.props;
-        const hideAddButton = step.dslName === 'StepDefinition' && !CamelDisplayUtil.isStepDefinitionExpanded(integration, step.uuid, selectedUuid.at(0));
+    function getAddStepButton() {
+        const { step} = props;
+        const hideAddButton = step.dslName === 'StepDefinition' && !CamelDisplayUtil.isStepDefinitionExpanded(integration, step.uuid, selectedUuids.at(0));
         if (hideAddButton) return (<></>)
         else return (
             <Tooltip position={"bottom"}
                      content={<div>{"Add step to " + CamelUi.getTitle(step)}</div>}>
-                <button type="button" aria-label="Add" onClick={e => this.openSelector(e)}
-                        className={this.isAddStepButtonLeft() ? "add-button add-button-left" : "add-button add-button-bottom"}>
+                <button type="button" aria-label="Add" onClick={e => onOpenSelector(e)}
+                        className={isAddStepButtonLeft() ? "add-button add-button-left" : "add-button add-button-bottom"}>
                     <AddIcon />
                 </button>
             </Tooltip>
         )
     }
 
-    getAddElementButton() {
+    function getAddElementButton() {
         return (
-            <Tooltip position={"bottom"} content={<div>{"Add DSL element to " + CamelUi.getTitle(this.props.step)}</div>}>
+            <Tooltip position={"bottom"} content={<div>{"Add DSL element to " + CamelUi.getTitle(props.step)}</div>}>
                 <button
                     type="button"
                     aria-label="Add"
-                    onClick={e => this.openSelector(e, false)}
+                    onClick={e => onOpenSelector(e, false)}
                     className={"add-element-button"}>
                     <AddIcon />
                 </button>
@@ -399,95 +398,92 @@ export class DslElement extends React.Component<Props, State> {
         )
     }
 
-    getInsertElementButton() {
+    function getInsertElementButton() {
         return (
             <Tooltip position={"left"} content={<div>{"Insert element before"}</div>}>
-                <button type="button" aria-label="Insert" onClick={e => this.openSelector(e, true, true)} className={"insert-element-button"}><InsertIcon />
+                <button type="button" aria-label="Insert" onClick={e => onOpenSelector(e, true, true)} className={"insert-element-button"}><InsertIcon />
                 </button>
             </Tooltip>
         )
     }
 
-    getDeleteButton() {
+    function getDeleteButton() {
         return (
             <Tooltip position={"right"} content={<div>{"Delete element"}</div>}>
-                <button type="button" aria-label="Delete" onClick={e => this.delete(e)} className="delete-button"><DeleteIcon /></button>
+                <button type="button" aria-label="Delete" onClick={e => onDeleteElement(e)} className="delete-button"><DeleteIcon /></button>
             </Tooltip>
         )
     }
 
-    getMoveConfirmation() {
+    function getMoveConfirmation() {
         return (
             <Modal
                 aria-label="title"
                 className='move-modal'
-                isOpen={this.state.showMoveConfirmation}
+                isOpen={showMoveConfirmation}
                 variant={ModalVariant.small}
             ><Flex direction={{default: "column"}}>
                 <div>Select move type:</div>
-                <Button key="place" variant="primary" onClick={event => this.confirmMove(false)}>Shift (target down)</Button>
-                <Button key="child" variant="secondary" onClick={event => this.confirmMove(true)}>Move as target step</Button>
-                <Button key="cancel" variant="tertiary" onClick={event => this.cancelMove()}>Cancel</Button>
+                <Button key="place" variant="primary" onClick={event => confirmMove(false)}>Shift (target down)</Button>
+                <Button key="child" variant="secondary" onClick={event => confirmMove(true)}>Move as target step</Button>
+                <Button key="cancel" variant="tertiary" onClick={event => cancelMove()}>Cancel</Button>
             </Flex>
 
             </Modal>
         )
     }
 
-    render() {
-        const element: CamelElement = this.props.step;
-        const className = "step-element" + (this.isSelected() ? " step-element-selected" : "")
-            + (!this.props.step.show ? " hidden-step" : "");
-        return (
-            <div key={"root" + element.uuid}
-                 className={className}
-                 ref={el => this.sendPosition(el, this.isSelected())}
-                 style={{
-                     borderStyle: this.hasBorder() ? "dotted" : "none",
-                     borderColor: this.isSelected() ? "var(--step-border-color-selected)" : "var(--step-border-color)",
-                     marginTop: this.isInStepWithChildren() ? "16px" : "8px",
-                     zIndex: element.dslName === 'ToDefinition' ? 20 : 10,
-                     boxShadow: this.state.isDraggedOver ? "0px 0px 1px 2px var(--step-border-color-selected)" : "none",
-                 }}
-                 onMouseOver={event => event.stopPropagation()}
-                 onClick={event => this.selectElement(event)}
-                 onDragStart={event => {
-                     event.stopPropagation();
-                     event.dataTransfer.setData("text/plain", element.uuid);
-                     (event.target as any).style.opacity = .5;
-                     this.setState({isDragging: true});
-                 }}
-                 onDragEnd={event => {
-                     (event.target as any).style.opacity = '';
-                     this.setState({isDragging: false});
-                 }}
-                 onDragOver={event => {
-                     event.preventDefault();
-                     event.stopPropagation();
-                     if (element.dslName !== 'FromDefinition' && !this.state.isDragging) {
-                         this.setState({isDraggedOver: true});
-                     }
-                 }}
-                 onDragEnter={event => {
-                     event.preventDefault();
-                     event.stopPropagation();
-                     if (element.dslName !== 'FromDefinition') {
-                         this.setState({isDraggedOver: true});
-                     }
-                 }}
-                 onDragLeave={event => {
-                     event.preventDefault();
-                     event.stopPropagation();
-                     this.setState({isDraggedOver: false});
-
-                 }}
-                 onDrop={event => this.dragElement(event, element)}
-                 draggable={!this.isNotDraggable()}
-            >
-                {this.getElementHeader()}
-                {this.getChildElements()}
-                {this.getMoveConfirmation()}
-            </div>
-        )
-    }
+    const element: CamelElement = props.step;
+    const className = "step-element" + (isSelected() ? " step-element-selected" : "")
+        + (!props.step.show ? " hidden-step" : "");
+    return (
+        <div key={"root" + element.uuid}
+             className={className}
+             ref={el => sendPosition(el, isSelected())}
+             style={{
+                 borderStyle: hasBorder() ? "dotted" : "none",
+                 borderColor: isSelected() ? "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()}
+            {getMoveConfirmation()}
+        </div>
+    )
 }
diff --git a/karavan-designer/src/designer/route/ElementResizeListener.tsx b/karavan-designer/src/designer/route/ElementResizeListener.tsx
new file mode 100644
index 00000000..4ef53c64
--- /dev/null
+++ b/karavan-designer/src/designer/route/ElementResizeListener.tsx
@@ -0,0 +1,75 @@
+/*
+ * 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 {RefObject, useCallback, useEffect, useRef} from "react";
+
+interface Props {
+    onResize: (event: Event) => void;
+};
+export const ElementResizeListener: React.FC<Props> = ({ onResize }) => {
+    const rafRef = useRef(0);
+    const objectRef: RefObject<HTMLObjectElement> = useRef(null);
+    const onResizeRef = useRef(onResize);
+
+    onResizeRef.current = onResize;
+
+    const _onResize = useCallback((e: Event) => {
+        if (rafRef.current) {
+            cancelAnimationFrame(rafRef.current);
+        }
+        rafRef.current = requestAnimationFrame(() => {
+            onResizeRef.current(e);
+        });
+    }, []);
+
+    const onLoad = useCallback(() => {
+        const obj = objectRef.current;
+        if (obj && obj.contentDocument && obj.contentDocument.defaultView) {
+            obj.contentDocument.defaultView.addEventListener('resize', _onResize);
+        }
+    }, []);
+
+    useEffect(() => {
+        return () => {
+            const obj = objectRef.current;
+            if (obj && obj.contentDocument && obj.contentDocument.defaultView) {
+                obj.contentDocument.defaultView.removeEventListener('resize', _onResize);
+            }
+        }
+    }, []);
+
+    return (
+        <object
+            aria-label="object"
+            onLoad={onLoad}
+            ref={objectRef} tabIndex={-1}
+            type={'text/html'}
+            data={'about:blank'}
+            title={''}
+            style={{
+                position: 'absolute',
+                top: 0,
+                left: 0,
+                height: '100%',
+                width: '100%',
+                pointerEvents: 'none',
+                zIndex: -1,
+                opacity: 0,
+            }}
+        />
+    )
+}
diff --git a/karavan-designer/src/designer/route/RouteDesigner.tsx b/karavan-designer/src/designer/route/RouteDesigner.tsx
index f7607b90..101309bb 100644
--- a/karavan-designer/src/designer/route/RouteDesigner.tsx
+++ b/karavan-designer/src/designer/route/RouteDesigner.tsx
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React from 'react';
+import React, {useCallback, useEffect, useRef, useState} from 'react';
 import {
     Drawer,
     DrawerPanelContent,
@@ -32,164 +32,156 @@ import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon";
 import {DslElement} from "./DslElement";
 import {CamelUi} from "../utils/CamelUi";
 import {CamelDisplayUtil} from "karavan-core/lib/api/CamelDisplayUtil";
-import {RouteDesignerLogic} from "./RouteDesignerLogic";
+import {useRouteDesignerHook} from "./useRouteDesignerHook";
+import {useDesignerStore, useIntegrationStore} from "../KaravanStore";
+import {shallow} from "zustand/shallow";
+import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
+import {RouteConfigurationDefinition} from "karavan-core/lib/model/CamelDefinition";
+import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
+import {ElementResizeListener} from "./ElementResizeListener";
 
 interface Props {
-    onSave?: (integration: Integration, propertyOnly: boolean) => void
-    integration: Integration
+    // onSave?: (integration: Integration, propertyOnly: boolean) => void
+    // integration: Integration
     dark: boolean
 }
 
-export interface RouteDesignerState {
-    logic: RouteDesignerLogic
-    integration: Integration
-    selectedStep?: CamelElement
-    showSelector: boolean
-    showDeleteConfirmation: boolean
-    deleteMessage: string
-    parentId: string
-    parentDsl?: string
-    selectedPosition?: number
-    showSteps: boolean
-    selectedUuids: string []
-    key: string
-    width: number
-    height: number
-    top: number
-    left: number
-    clipboardSteps: CamelElement[]
-    shiftKeyPressed?: boolean
-    ref?: any
-    printerRef?: any
-    propertyOnly: boolean
-    selectorTabIndex?: string | number
-}
+export const RouteDesigner = (props: Props) => {
 
-export class RouteDesigner extends React.Component<Props, RouteDesignerState> {
+    const ref = React.createRef();
+    const printerRef = React.createRef()
+    const contentRef: React.RefObject<HTMLDivElement> = useRef(null);
+    const flowRef: React.RefObject<HTMLDivElement> = useRef(null);
 
-    public state: RouteDesignerState = {
-        logic: new RouteDesignerLogic(this),
-        integration: CamelDisplayUtil.setIntegrationVisibility(this.props.integration, undefined),
-        showSelector: false,
-        showDeleteConfirmation: false,
-        deleteMessage: '',
-        parentId: '',
-        showSteps: true,
-        selectedUuids: [],
-        clipboardSteps: [],
-        key: "",
-        width: 1000,
-        height: 1000,
-        top: 0,
-        left: 0,
-        ref: React.createRef(),
-        printerRef: React.createRef(),
-        propertyOnly: false,
-    };
+    const { deleteElement, selectElement, moveElement, onShowDeleteConfirmation, onDslSelect, openSelector, createRouteConfiguration} = useRouteDesignerHook();
 
-    componentDidMount() {
-        this.state.logic.componentDidMount();
-    }
+    const [integration, setIntegration] = useIntegrationStore((state) => [state.integration, state.setIntegration], shallow)
+    const [showSelector, showDeleteConfirmation, propertyOnly, showSteps,deleteMessage,parentId, selectedUuids,clipboardSteps, parentDsl,selectedPosition,selectedStep,selectorTabIndex,
+        setShowSelector, setShowDeleteConfirmation,setPropertyOnly,setShowSteps,setDeleteMessage, setParentId, setSelectedUuids, setClipboardSteps,setPosition,
+        width, height, top, left] = useDesignerStore((s) =>
+        [s.showSelector, s.showDeleteConfirmation, s.propertyOnly, s.showSteps,s.deleteMessage,s.parentId, s.selectedUuids,s.clipboardSteps, s.parentDsl, s.selectedPosition, s.selectedStep,s.selectorTabIndex,
+            s.setShowSelector, s.setShowDeleteConfirmation,s.setPropertyOnly,s.setShowSteps,s.setDeleteMessage, s.setParentId, s.setSelectedUuids, s.setClipboardSteps, s.setPosition,
+            s.width, s.height, s.top, s.left], shallow)
 
-    componentWillUnmount() {
-        this.state.logic.componentWillUnmount();
-    }
+    useEffect(() => {
+        adaptResize();
+    })
 
-    handleResize = (event: any) => {
-        return this.state.logic.handleResize(event);
-    }
+    // function componentDidMount() {
+    //     logic.componentDidMount();
+    // }
+    //
+    // function componentWillUnmount() {
+    //     logic.componentWillUnmount();
+    // }
+    //
+    // function handleResize = (event: any) => {
+    //     return logic.handleResize(event);
+    // }
+    //
+    // function handleKeyDown = (event: KeyboardEvent) => {
+    //     return logic.handleKeyDown(event);
+    // }
+    //
+    // function handleKeyUp = (event: KeyboardEvent) => {
+    //     return logic.handleKeyUp(event);
+    // }
+    //
+    // function componentDidUpdate = (prevProps: Readonly<Props>, prevState: Readonly<RouteDesignerState>, snapshot?: any) => {
+    //     return logic.componentDidUpdate(prevState, snapshot);
+    // }
 
-    handleKeyDown = (event: KeyboardEvent) => {
-        return this.state.logic.handleKeyDown(event);
-    }
-
-    handleKeyUp = (event: KeyboardEvent) => {
-        return this.state.logic.handleKeyUp(event);
-    }
-
-    componentDidUpdate = (prevProps: Readonly<Props>, prevState: Readonly<RouteDesignerState>, snapshot?: any) => {
-        return this.state.logic.componentDidUpdate(prevState, snapshot);
-    }
-
-    getSelectorModal() {
+    function getSelectorModal() {
         return (
             <DslSelector
-                isOpen={this.state.showSelector}
-                onClose={() => this.state.logic.closeDslSelector()}
-                dark={this.props.dark}
-                parentId={this.state.parentId}
-                parentDsl={this.state.parentDsl}
-                showSteps={this.state.showSteps}
-                position={this.state.selectedPosition}
-                tabIndex={this.state.selectorTabIndex}
-                onDslSelect={this.state.logic.onDslSelect}/>)
+                isOpen={showSelector}
+                onClose={() => setShowSelector(false)}
+                dark={props.dark}
+                parentId={parentId}
+                parentDsl={parentDsl}
+                showSteps={showSteps}
+                position={selectedPosition}
+                tabIndex={selectorTabIndex}
+                onDslSelect={(dsl, parentId1) => onDslSelect(dsl, parentId1)}/>)
     }
 
-    getDeleteConfirmation() {
-        let htmlContent: string = this.state.deleteMessage;
+    function getDeleteConfirmation() {
+        let htmlContent: string = deleteMessage;
         return (<Modal
             className="modal-delete"
             title="Confirmation"
-            isOpen={this.state.showDeleteConfirmation}
-            onClose={() => this.setState({showDeleteConfirmation: false})}
+            isOpen={showDeleteConfirmation}
+            onClose={() => setShowDeleteConfirmation(false)}
             actions={[
-                <Button key="confirm" variant="primary" onClick={e => this.state.logic.deleteElement()}>Delete</Button>,
+                <Button key="confirm" variant="primary" onClick={e => deleteElement()}>Delete</Button>,
                 <Button key="cancel" variant="link"
-                        onClick={e => this.setState({showDeleteConfirmation: false})}>Cancel</Button>
+                        onClick={e => setShowDeleteConfirmation(false)}>Cancel</Button>
             ]}
-            onEscapePress={e => this.setState({showDeleteConfirmation: false})}>
+            onEscapePress={e => setShowDeleteConfirmation(false)}>
             <div>
                 {htmlContent}
             </div>
         </Modal>)
     }
 
-    getPropertiesPanel() {
+    function getPropertiesPanel() {
         return (
-            <DrawerPanelContent onResize={(_event, width) => this.setState({key: Math.random().toString()})}
+            <DrawerPanelContent onResize={(_event, width) => {
+                // setState({key: Math.random().toString()})
+            }}
                                 style={{transform: "initial"}} isResizable hasNoBorder defaultSize={'400px'}
                                 maxSize={'800px'} minSize={'300px'}>
-                <DslProperties ref={this.state.ref}
-                               integration={this.state.integration}
-                               step={this.state.selectedStep}
-                               onIntegrationUpdate={this.state.logic.onIntegrationUpdate}
-                               onPropertyUpdate={this.state.logic.onPropertyUpdate}
-                               isRouteDesigner={true}
-                               dark={this.props.dark}/>
+                {/*<DslProperties ref={ref}*/}
+                {/*               integration={integration}*/}
+                {/*               step={selectedStep}*/}
+                {/*               onIntegrationUpdate={logic.onIntegrationUpdate}*/}
+                {/*               onPropertyUpdate={logic.onPropertyUpdate}*/}
+                {/*               isRouteDesigner={true}*/}
+                {/*               dark={props.dark}/>*/}
             </DrawerPanelContent>
         )
     }
 
-    getGraph() {
-        const {selectedUuids, integration, key, width, height, top, left} = this.state;
+    // function onResizePage(el: HTMLDivElement | null) {
+    //     console.log("onResizePage", el)
+    //     const rect = el?.getBoundingClientRect();
+    //     if (el && rect && (el.scrollWidth !== width || el.scrollHeight !== height || rect.top !== top || rect.left !== left)) {
+    //         setPosition(el.scrollWidth, el.scrollHeight, rect.top, rect.left)
+    //     }
+    // }
+
+    const adaptResize = useCallback(() => {
+        if (contentRef.current && flowRef.current) {
+            const el = contentRef.current.getBoundingClientRect();
+            // flowRef.current.textContent = `width: ${elmRect.width}`;
+            console.log("elmRect", el)
+            setPosition(el.width, el.height, el.top, el.left)
+        }
+    }, []);
+
+
+    function getGraph() {
         const routes = CamelUi.getRoutes(integration);
         const routeConfigurations = CamelUi.getRouteConfigurations(integration);
         return (
-            <div ref={this.state.printerRef} className="graph">
-                <DslConnections height={height} width={width} top={top} left={left} integration={integration}/>
-                <div className="flows" data-click="FLOWS" onClick={event => this.state.logic.unselectElement(event)}
-                     ref={el => this.state.logic.onResizePage(el)}>
+            <div className="graph" ref={contentRef}>
+                <DslConnections/>
+                <ElementResizeListener onResize={adaptResize}/>
+                <div id="flows" className="flows" data-click="FLOWS" onClick={event => {
+                    // logic.unselectElement(event)
+                }}
+                     // ref={el => onResizePage(el)}
+                     ref={flowRef}
+                >
                     {routeConfigurations?.map((routeConfiguration, index: number) => (
-                        <DslElement key={routeConfiguration.uuid + key}
-                                    integration={integration}
-                                    openSelector={this.state.logic.openSelector}
-                                    deleteElement={this.state.logic.showDeleteConfirmation}
-                                    selectElement={this.state.logic.selectElement}
-                                    moveElement={this.state.logic.moveElement}
-                                    selectedUuid={selectedUuids}
+                        <DslElement key={routeConfiguration.uuid}
                                     inSteps={false}
                                     position={index}
                                     step={routeConfiguration}
                                     parent={undefined}/>
                     ))}
                     {routes?.map((route: any, index: number) => (
-                        <DslElement key={route.uuid + key}
-                                    integration={integration}
-                                    openSelector={this.state.logic.openSelector}
-                                    deleteElement={this.state.logic.showDeleteConfirmation}
-                                    selectElement={this.state.logic.selectElement}
-                                    moveElement={this.state.logic.moveElement}
-                                    selectedUuid={selectedUuids}
+                        <DslElement key={route.uuid}
                                     inSteps={false}
                                     position={index}
                                     step={route}
@@ -199,31 +191,35 @@ export class RouteDesigner extends React.Component<Props, RouteDesignerState> {
                         <Button
                             variant={routes.length === 0 ? "primary" : "secondary"}
                             icon={<PlusIcon/>}
-                            onClick={e => this.state.logic.openSelector(undefined, undefined)}>Create route
+                            onClick={e => {
+                                openSelector(undefined, undefined)
+                            }}
+                        >
+                            Create route
                         </Button>
                         <Button
                             variant="secondary"
                             icon={<PlusIcon/>}
-                            onClick={e => this.state.logic.createRouteConfiguration()}>Create configuration
+                            onClick={e => createRouteConfiguration()}
+                        >
+                            Create configuration
                         </Button>
                     </div>
                 </div>
             </div>)
     }
 
-    render() {
-        return (
-            <PageSection className="dsl-page" isFilled padding={{default: 'noPadding'}}>
-                <div className="dsl-page-columns">
-                    <Drawer isExpanded isInline>
-                        <DrawerContent panelContent={this.getPropertiesPanel()}>
-                            <DrawerContentBody>{this.getGraph()}</DrawerContentBody>
-                        </DrawerContent>
-                    </Drawer>
-                </div>
-                {this.state.showSelector === true && this.getSelectorModal()}
-                {this.getDeleteConfirmation()}
-            </PageSection>
-        );
-    }
+    return (
+        <PageSection className="dsl-page" isFilled padding={{default: 'noPadding'}}>
+            <div className="dsl-page-columns">
+                <Drawer isExpanded isInline>
+                    <DrawerContent panelContent={getPropertiesPanel()}>
+                        <DrawerContentBody>{getGraph()}</DrawerContentBody>
+                    </DrawerContent>
+                </Drawer>
+            </div>
+            {getSelectorModal()}
+            {getDeleteConfirmation()}
+        </PageSection>
+    );
 }
\ No newline at end of file
diff --git a/karavan-designer/src/designer/route/RouteDesignerLogic.tsx b/karavan-designer/src/designer/route/RouteDesignerLogic.tsx
deleted file mode 100644
index b352a056..00000000
--- a/karavan-designer/src/designer/route/RouteDesignerLogic.tsx
+++ /dev/null
@@ -1,396 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import React from 'react';
-import '../karavan.css';
-import {DslMetaModel} from "../utils/DslMetaModel";
-import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
-import {ChoiceDefinition, FromDefinition, LogDefinition, RouteConfigurationDefinition, RouteDefinition} from "karavan-core/lib/model/CamelDefinition";
-import {CamelElement, Integration} from "karavan-core/lib/model/IntegrationDefinition";
-import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
-import {CamelDefinitionApi} from "karavan-core/lib/api/CamelDefinitionApi";
-import {Command, EventBus} from "../utils/EventBus";
-import {RouteToCreate} from "../utils/CamelUi";
-import {CamelDisplayUtil} from "karavan-core/lib/api/CamelDisplayUtil";
-import {toPng} from 'html-to-image';
-import {RouteDesigner, RouteDesignerState} from "./RouteDesigner";
-import {findDOMNode} from "react-dom";
-import {Subscription} from "rxjs";
-import debounce from 'lodash.debounce';
-
-export class RouteDesignerLogic {
-
-    routeDesigner: RouteDesigner
-    commandSub?: Subscription
-
-    constructor(routeDesigner: RouteDesigner) {
-        this.routeDesigner = routeDesigner;
-    }
-
-    componentDidMount() {
-        window.addEventListener('resize', this.routeDesigner.handleResize);
-        window.addEventListener('keydown', this.routeDesigner.handleKeyDown);
-        window.addEventListener('keyup', this.routeDesigner.handleKeyUp);
-        const element = findDOMNode(this.routeDesigner.state.ref.current)?.parentElement?.parentElement;
-        const checkResize = (mutations: any) => {
-            const el = mutations[0].target;
-            const w = el.clientWidth;
-            const isChange = mutations.map((m: any) => `${m.oldValue}`).some((prev: any) => prev.indexOf(`width: ${w}px`) === -1);
-            if (isChange) this.routeDesigner.setState({key: Math.random().toString()});
-        }
-        if (element) {
-            const observer = new MutationObserver(checkResize);
-            observer.observe(element, {attributes: true, attributeOldValue: true, attributeFilter: ['style']});
-        }
-        this.commandSub = EventBus.onCommand()?.subscribe((command: Command) => this.onCommand(command));
-    }
-
-    componentWillUnmount() {
-        window.removeEventListener('resize', this.routeDesigner.handleResize);
-        window.removeEventListener('keydown', this.routeDesigner.handleKeyDown);
-        window.removeEventListener('keyup', this.routeDesigner.handleKeyUp);
-        this.commandSub?.unsubscribe();
-    }
-
-    handleResize = (event: any) => {
-        this.routeDesigner.setState({key: Math.random().toString()});
-    }
-
-    handleKeyDown = (event: KeyboardEvent) => {
-        if ((event.shiftKey)) {
-            this.routeDesigner.setState({shiftKeyPressed: true});
-        }
-        if (window.document.hasFocus() && window.document.activeElement) {
-            if (['BODY', 'MAIN'].includes(window.document.activeElement.tagName)) {
-                let charCode = String.fromCharCode(event.which).toLowerCase();
-                if ((event.ctrlKey || event.metaKey) && charCode === 'c') {
-                    this.copyToClipboard();
-                } else if ((event.ctrlKey || event.metaKey) && charCode === 'v') {
-                    this.pasteFromClipboard();
-                }
-            }
-        } else {
-            if (event.repeat) {
-                window.dispatchEvent(event);
-            }
-        }
-    }
-
-    handleKeyUp = (event: KeyboardEvent) => {
-        this.routeDesigner.setState({shiftKeyPressed: false});
-        if (event.repeat) {
-            window.dispatchEvent(event);
-        }
-    }
-
-    componentDidUpdate = (prevState: Readonly<RouteDesignerState>, snapshot?: any) => {
-        if (prevState.key !== this.routeDesigner.state.key) {
-            this.routeDesigner.props.onSave?.call(this, this.routeDesigner.state.integration, this.routeDesigner.state.propertyOnly);
-        }
-    }
-
-    copyToClipboard = (): void => {
-        const {integration, selectedUuids} = this.routeDesigner.state;
-        const steps: CamelElement[] = []
-        selectedUuids.forEach(selectedUuid => {
-            const selectedElement = CamelDefinitionApiExt.findElementInIntegration(integration, selectedUuid);
-            if (selectedElement) {
-                steps.push(selectedElement);
-            }
-        })
-        if (steps.length >0) {
-            this.routeDesigner.setState(prevState => ({
-                key: Math.random().toString(),
-                clipboardSteps: [...steps]
-            }));
-        }
-    }
-    pasteFromClipboard = (): void => {
-        const {integration, selectedUuids, clipboardSteps} = this.routeDesigner.state;
-        if (clipboardSteps.length === 1 && clipboardSteps[0]?.dslName === 'FromDefinition') {
-            const clone = CamelUtil.cloneStep(clipboardSteps[0], true);
-            const route = CamelDefinitionApi.createRouteDefinition({from: clone});
-            this.addStep(route, '', 0)
-        } else if (clipboardSteps.length === 1 && clipboardSteps[0]?.dslName === 'RouteDefinition') {
-            const clone = CamelUtil.cloneStep(clipboardSteps[0], true);
-            this.addStep(clone, '', 0)
-        } else if (selectedUuids.length === 1) {
-            const targetMeta = CamelDefinitionApiExt.findElementMetaInIntegration(integration, selectedUuids[0]);
-            clipboardSteps.reverse().forEach(clipboardStep => {
-                if (clipboardStep && targetMeta.parentUuid) {
-                    const clone = CamelUtil.cloneStep(clipboardStep, true);
-                    this.addStep(clone, targetMeta.parentUuid, targetMeta.position);
-                }
-            })
-        }
-    }
-
-    onCommand = (command: Command) => {
-        switch (command.command){
-            case "downloadImage": this.integrationImageDownload()
-        }
-    }
-
-    onPropertyUpdate = debounce((element: CamelElement, newRoute?: RouteToCreate) => {
-        if (newRoute) {
-            let i = CamelDefinitionApiExt.updateIntegrationRouteElement(this.routeDesigner.state.integration, element);
-            const f = CamelDefinitionApi.createFromDefinition({uri: newRoute.componentName, parameters: {name: newRoute.name}});
-            const r = CamelDefinitionApi.createRouteDefinition({from: f, id: newRoute.name})
-            i = CamelDefinitionApiExt.addStepToIntegration(i, r, '');
-            const clone = CamelUtil.cloneIntegration(i);
-            this.routeDesigner.setState(prevState => ({
-                integration: clone,
-                key: Math.random().toString(),
-                showSelector: false,
-                selectedStep: element,
-                propertyOnly: false,
-                selectedUuids: [element.uuid]
-            }));
-        } else {
-            const clone = CamelUtil.cloneIntegration(this.routeDesigner.state.integration);
-            const i = CamelDefinitionApiExt.updateIntegrationRouteElement(clone, element);
-            this.routeDesigner.setState({integration: i, propertyOnly: true, key: Math.random().toString()});
-        }
-    }, 300, {leading: true})
-
-    showDeleteConfirmation = (id: string) => {
-        let message: string;
-        const uuidsToDelete:string [] = [id];
-        let ce: CamelElement;
-        ce = CamelDefinitionApiExt.findElementInIntegration(this.routeDesigner.state.integration, id)!;
-        if (ce.dslName === 'FromDefinition') { // Get the RouteDefinition for this.routeDesigner.  Use its uuid.
-            let flows = this.routeDesigner.state.integration.spec.flows!;
-            for (let i = 0; i < flows.length; i++) {
-                if (flows[i].dslName === 'RouteDefinition') {
-                    let routeDefinition: RouteDefinition = flows[i];
-                    if (routeDefinition.from.uuid === id) {
-                        uuidsToDelete.push(routeDefinition.uuid);
-                        break;
-                    }
-                }
-            }
-            message = 'Deleting the first element will delete the entire route!';
-        } else if (ce.dslName === 'RouteDefinition') {
-            message = 'Delete route?';
-        } else if (ce.dslName === 'RouteConfigurationDefinition') {
-            message = 'Delete route configuration?';
-        } else {
-            message = 'Delete element from route?';
-        }
-        this.routeDesigner.setState(prevState => ({
-            showSelector: false,
-            showDeleteConfirmation: true,
-            deleteMessage: message,
-            selectedUuids: uuidsToDelete,
-        }));
-    }
-
-    deleteElement = () => {
-        this.routeDesigner.state.selectedUuids.forEach(uuidToDelete => {
-            const i = CamelDefinitionApiExt.deleteStepFromIntegration(this.routeDesigner.state.integration, uuidToDelete);
-            this.routeDesigner.setState(prevState => ({
-                integration: i,
-                showSelector: false,
-                showDeleteConfirmation: false,
-                deleteMessage: '',
-                key: Math.random().toString(),
-                selectedStep: undefined,
-                propertyOnly: false,
-                selectedUuids: [uuidToDelete],
-            }));
-            const el = new CamelElement("");
-            el.uuid = uuidToDelete;
-            EventBus.sendPosition("delete", el, undefined, new DOMRect(), new DOMRect(), 0);
-        });
-    }
-
-    selectElement = (element: CamelElement) => {
-        const {shiftKeyPressed, selectedUuids, integration} = this.routeDesigner.state;
-        let canNotAdd: boolean = false;
-        if (shiftKeyPressed) {
-            const hasFrom = selectedUuids.map(e => CamelDefinitionApiExt.findElementInIntegration(integration, e)?.dslName === 'FromDefinition').filter(r => r).length > 0;
-            canNotAdd = hasFrom || (selectedUuids.length > 0 && element.dslName === 'FromDefinition');
-        }
-        const add = shiftKeyPressed && !selectedUuids.includes(element.uuid);
-        const remove = shiftKeyPressed && selectedUuids.includes(element.uuid);
-        const i = CamelDisplayUtil.setIntegrationVisibility(this.routeDesigner.state.integration, element.uuid);
-        this.routeDesigner.setState((prevState: RouteDesignerState) => {
-            if (remove) {
-                const index = prevState.selectedUuids.indexOf(element.uuid);
-                prevState.selectedUuids.splice(index, 1);
-            } else if (add && !canNotAdd) {
-                prevState.selectedUuids.push(element.uuid);
-            }
-            const uuid: string = prevState.selectedUuids.includes(element.uuid) ? element.uuid : prevState.selectedUuids.at(0) || '';
-            const selectedElement = shiftKeyPressed ? CamelDefinitionApiExt.findElementInIntegration(integration, uuid) : element;
-            return {
-                integration: i,
-                selectedStep: selectedElement,
-                showSelector: false,
-                selectedUuids: shiftKeyPressed ? [...prevState.selectedUuids] : [element.uuid],
-            }
-        });
-    }
-
-    unselectElement = (evt: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
-        if ((evt.target as any).dataset.click === 'FLOWS') {
-            evt.stopPropagation()
-            const i = CamelDisplayUtil.setIntegrationVisibility(this.routeDesigner.state.integration, undefined);
-            this.routeDesigner.setState(prevState => ({
-                integration: i,
-                selectedStep: undefined,
-                showSelector: false,
-                selectedPosition: undefined,
-                selectedUuids: [],
-            }));
-        }
-    }
-
-    openSelector = (parentId: string | undefined, parentDsl: string | undefined, showSteps: boolean = true, position?: number | undefined, selectorTabIndex?: string | number) => {
-        this.routeDesigner.setState({
-            showSelector: true,
-            parentId: parentId || '',
-            parentDsl: parentDsl,
-            showSteps: showSteps,
-            selectedPosition: position,
-            selectorTabIndex: selectorTabIndex
-        })
-    }
-
-    closeDslSelector = () => {
-        this.routeDesigner.setState({showSelector: false})
-    }
-
-    onDslSelect = (dsl: DslMetaModel, parentId: string, position?: number | undefined) => {
-        switch (dsl.dsl) {
-            case 'FromDefinition' :
-                const route = CamelDefinitionApi.createRouteDefinition({from: new FromDefinition({uri: dsl.uri})});
-                this.addStep(route, parentId, position)
-                break;
-            case 'ToDefinition' :
-                const to = CamelDefinitionApi.createStep(dsl.dsl, {uri: dsl.uri});
-                this.addStep(to, parentId, position)
-                break;
-            case 'ToDynamicDefinition' :
-                const toD = CamelDefinitionApi.createStep(dsl.dsl, {uri: dsl.uri});
-                this.addStep(toD, parentId, position)
-                break;
-            case 'KameletDefinition' :
-                const kamelet = CamelDefinitionApi.createStep(dsl.dsl, {name: dsl.name});
-                this.addStep(kamelet, parentId, position)
-                break;
-            default:
-                const step = CamelDefinitionApi.createStep(dsl.dsl, undefined);
-                const augmentedStep = this.setDslDefaults(step);
-                this.addStep(augmentedStep, parentId, position)
-                break;
-        }
-    }
-
-    setDslDefaults(step: CamelElement): CamelElement {
-        if (step.dslName === 'LogDefinition') {
-            // eslint-disable-next-line no-template-curly-in-string
-            (step as LogDefinition).message = "${body}";
-        }
-        if (step.dslName === 'ChoiceDefinition') {
-            (step as ChoiceDefinition).when?.push(CamelDefinitionApi.createStep('WhenDefinition', undefined));
-            (step as ChoiceDefinition).otherwise = CamelDefinitionApi.createStep('OtherwiseDefinition', undefined);
-        }
-        return step;
-    }
-
-    createRouteConfiguration = () => {
-        const clone = CamelUtil.cloneIntegration(this.routeDesigner.state.integration);
-        const routeConfiguration = new RouteConfigurationDefinition();
-        const i = CamelDefinitionApiExt.addRouteConfigurationToIntegration(clone, routeConfiguration);
-        this.routeDesigner.setState(prevState => ({
-            integration: i,
-            propertyOnly: false,
-            key: Math.random().toString(),
-            selectedStep: routeConfiguration,
-            selectedUuids: [routeConfiguration.uuid],
-        }));
-    }
-
-    addStep = (step: CamelElement, parentId: string, position?: number | undefined) => {
-        const i = CamelDefinitionApiExt.addStepToIntegration(this.routeDesigner.state.integration, step, parentId, position);
-        const clone = CamelUtil.cloneIntegration(i);
-        EventBus.sendPosition("clean", step, undefined, new DOMRect(), new DOMRect(), 0);
-        const selectedStep = step.dslName === 'RouteDefinition' ? (step as RouteDefinition).from  : step;
-        this.routeDesigner.setState(prevState => ({
-            integration: clone,
-            key: Math.random().toString(),
-            showSelector: false,
-            selectedStep: selectedStep,
-            propertyOnly: false,
-            selectedUuids: [selectedStep.uuid],
-        }));
-    }
-
-    onIntegrationUpdate = (i: Integration) => {
-        this.routeDesigner.setState({integration: i, propertyOnly: false, showSelector: false, key: Math.random().toString()});
-    }
-
-    moveElement = (source: string, target: string, asChild: boolean) => {
-        const i = CamelDefinitionApiExt.moveRouteElement(this.routeDesigner.state.integration, source, target, asChild);
-        const clone = CamelUtil.cloneIntegration(i);
-        const selectedStep = CamelDefinitionApiExt.findElementInIntegration(clone, source);
-        this.routeDesigner.setState(prevState => ({
-            integration: clone,
-            key: Math.random().toString(),
-            showSelector: false,
-            selectedStep: selectedStep,
-            propertyOnly: false,
-            selectedUuids: [source],
-        }));
-    }
-
-    onResizePage(el: HTMLDivElement | null) {
-        const rect = el?.getBoundingClientRect();
-        if (el && rect && (el.scrollWidth !== this.routeDesigner.state.width || el.scrollHeight !== this.routeDesigner.state.height || rect.top !== this.routeDesigner.state.top || rect.left !== this.routeDesigner.state.left)) {
-            this.routeDesigner.setState({width: el.scrollWidth, height: el.scrollHeight, top: rect.top, left: rect.left})
-        }
-    }
-
-    downloadIntegrationImage(dataUrl: string) {
-        const a = document.createElement('a');
-        a.setAttribute('download', 'karavan-routes.png');
-        a.setAttribute('href', dataUrl);
-        a.click();
-    }
-
-    integrationImageDownloadFilter = (node: HTMLElement) => {
-        const exclusionClasses = ['add-flow'];
-        return !exclusionClasses.some(classname => {
-            return node.classList === undefined ? false : node.classList.contains(classname);
-        });
-    }
-
-    integrationImageDownload() {
-        if (this.routeDesigner.state.printerRef.current === null) {
-            return
-        }
-        toPng(this.routeDesigner.state.printerRef.current, {
-            style: {overflow: 'hidden'}, cacheBust: true, filter: this.integrationImageDownloadFilter,
-            height: this.routeDesigner.state.height, width: this.routeDesigner.state.width, backgroundColor: this.routeDesigner.props.dark ? "black" : "white"
-        }).then(v => {
-            toPng(this.routeDesigner.state.printerRef.current, {
-                style: {overflow: 'hidden'}, cacheBust: true, filter: this.integrationImageDownloadFilter,
-                height: this.routeDesigner.state.height, width: this.routeDesigner.state.width, backgroundColor: this.routeDesigner.props.dark ? "black" : "white"
-            }).then(this.downloadIntegrationImage);
-        })
-    }
-}
\ No newline at end of file
diff --git a/karavan-designer/src/designer/route/property/DslPropertyField.tsx b/karavan-designer/src/designer/route/property/DslPropertyField.tsx
index 3021baf2..0dca7151 100644
--- a/karavan-designer/src/designer/route/property/DslPropertyField.tsx
+++ b/karavan-designer/src/designer/route/property/DslPropertyField.tsx
@@ -264,7 +264,7 @@ export class DslPropertyField extends React.Component<Props, State> {
             } else {
                 this.setState({customCode: value, showEditor: true})
             }
-        }).catch(reason => console.log(reason))
+        }).catch((reason: any) => console.log(reason))
     }
 
     getJavaTypeGeneratedInput = (property: PropertyMeta, value: any) => {
diff --git a/karavan-designer/src/designer/route/useRouteDesignerHook.tsx b/karavan-designer/src/designer/route/useRouteDesignerHook.tsx
new file mode 100644
index 00000000..fd97b731
--- /dev/null
+++ b/karavan-designer/src/designer/route/useRouteDesignerHook.tsx
@@ -0,0 +1,384 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import React from 'react';
+import '../karavan.css';
+import {DslMetaModel} from "../utils/DslMetaModel";
+import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
+import {ChoiceDefinition, FromDefinition, LogDefinition, RouteConfigurationDefinition, RouteDefinition} from "karavan-core/lib/model/CamelDefinition";
+import {CamelElement, Integration} from "karavan-core/lib/model/IntegrationDefinition";
+import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
+import {CamelDefinitionApi} from "karavan-core/lib/api/CamelDefinitionApi";
+import {Command, EventBus} from "../utils/EventBus";
+import {RouteToCreate} from "../utils/CamelUi";
+import {CamelDisplayUtil} from "karavan-core/lib/api/CamelDisplayUtil";
+import {toPng} from 'html-to-image';
+import {RouteDesigner} from "./RouteDesigner";
+import {findDOMNode} from "react-dom";
+import {Subscription} from "rxjs";
+import debounce from 'lodash.debounce';
+import {useDesignerStore, useIntegrationStore} from "../KaravanStore";
+import {shallow} from "zustand/shallow";
+import {setTabIndex} from "@patternfly/react-core";
+
+export const useRouteDesignerHook = () => {
+
+    const [integration, setIntegration] = useIntegrationStore((state) => [state.integration, state.setIntegration], shallow)
+    const [showSelector, showDeleteConfirmation, propertyOnly, showSteps,deleteMessage,parentId, selectedUuids,clipboardSteps, parentDsl,selectedPosition,selectedStep,selectorTabIndex,shiftKeyPressed,
+        setShowSelector, setShowDeleteConfirmation,setPropertyOnly,setShowSteps, setDeleteMessage, setSelectedStep, setParentId, setParentDsl, setSelectedPosition, setSelectedUuids, setClipboardSteps,setPosition,setSelectorTabIndex,
+        width, height, top, left] = useDesignerStore((s) =>
+        [s.showSelector, s.showDeleteConfirmation, s.propertyOnly, s.showSteps,s.deleteMessage,s.parentId, s.selectedUuids,s.clipboardSteps, s.parentDsl, s.selectedPosition, s.selectedStep,s.selectorTabIndex, s.shiftKeyPressed,
+            s.setShowSelector, s.setShowDeleteConfirmation,s.setPropertyOnly,s.setShowSteps,s.setDeleteMessage, s.setSelectedStep, s.setParentId, s.setParentDsl, s.setSelectedPosition, s.setSelectedUuids, s.setClipboardSteps, s.setPosition,s.setSelectorTabIndex,
+            s.width, s.height, s.top, s.left], shallow)
+
+    // const commandSub = EventBus.onCommand()?.subscribe((command: Command) => onCommand(command));
+    //
+    //
+    // function componentDidMount() {
+    //     window.addEventListener('resize', this.routeDesigner.handleResize);
+    //     window.addEventListener('keydown', this.routeDesigner.handleKeyDown);
+    //     window.addEventListener('keyup', this.routeDesigner.handleKeyUp);
+    //     const element = findDOMNode(this.routeDesigner.state.ref.current)?.parentElement?.parentElement;
+    //     const checkResize = (mutations: any) => {
+    //         const el = mutations[0].target;
+    //         const w = el.clientWidth;
+    //         const isChange = mutations.map((m: any) => `${m.oldValue}`).some((prev: any) => prev.indexOf(`width: ${w}px`) === -1);
+    //         if (isChange) this.routeDesigner.setState({key: Math.random().toString()});
+    //     }
+    //     if (element) {
+    //         const observer = new MutationObserver(checkResize);
+    //         observer.observe(element, {attributes: true, attributeOldValue: true, attributeFilter: ['style']});
+    //     }
+    //     this.commandSub = EventBus.onCommand()?.subscribe((command: Command) => this.onCommand(command));
+    // }
+    //
+    // function componentWillUnmount() {
+    //     window.removeEventListener('resize', this.routeDesigner.handleResize);
+    //     window.removeEventListener('keydown', this.routeDesigner.handleKeyDown);
+    //     window.removeEventListener('keyup', this.routeDesigner.handleKeyUp);
+    //     this.commandSub?.unsubscribe();
+    // }
+    //
+    // function handleResize = (event: any) => {
+    //     this.routeDesigner.setState({key: Math.random().toString()});
+    // }
+    //
+    // function handleKeyDown = (event: KeyboardEvent) => {
+    //     if ((event.shiftKey)) {
+    //         this.routeDesigner.setState({shiftKeyPressed: true});
+    //     }
+    //     if (window.document.hasFocus() && window.document.activeElement) {
+    //         if (['BODY', 'MAIN'].includes(window.document.activeElement.tagName)) {
+    //             let charCode = String.fromCharCode(event.which).toLowerCase();
+    //             if ((event.ctrlKey || event.metaKey) && charCode === 'c') {
+    //                 this.copyToClipboard();
+    //             } else if ((event.ctrlKey || event.metaKey) && charCode === 'v') {
+    //                 this.pasteFromClipboard();
+    //             }
+    //         }
+    //     } else {
+    //         if (event.repeat) {
+    //             window.dispatchEvent(event);
+    //         }
+    //     }
+    // }
+    //
+    // function handleKeyUp = (event: KeyboardEvent) => {
+    //     this.routeDesigner.setState({shiftKeyPressed: false});
+    //     if (event.repeat) {
+    //         window.dispatchEvent(event);
+    //     }
+    // }
+    //
+    // function componentDidUpdate = (prevState: Readonly<RouteDesignerState>, snapshot?: any) => {
+    //     if (prevState.key !== this.routeDesigner.state.key) {
+    //         this.routeDesigner.props.onSave?.call(this, this.routeDesigner.state.integration, this.routeDesigner.state.propertyOnly);
+    //     }
+    // }
+    //
+    // copyToClipboard = (): void => {
+    //     const {integration, selectedUuids} = this.routeDesigner.state;
+    //     const steps: CamelElement[] = []
+    //     selectedUuids.forEach(selectedUuid => {
+    //         const selectedElement = CamelDefinitionApiExt.findElementInIntegration(integration, selectedUuid);
+    //         if (selectedElement) {
+    //             steps.push(selectedElement);
+    //         }
+    //     })
+    //     if (steps.length >0) {
+    //         this.routeDesigner.setState(prevState => ({
+    //             key: Math.random().toString(),
+    //             clipboardSteps: [...steps]
+    //         }));
+    //     }
+    // }
+    // function pasteFromClipboard = (): void => {
+    //     const {integration, selectedUuids, clipboardSteps} = this.routeDesigner.state;
+    //     if (clipboardSteps.length === 1 && clipboardSteps[0]?.dslName === 'FromDefinition') {
+    //         const clone = CamelUtil.cloneStep(clipboardSteps[0], true);
+    //         const route = CamelDefinitionApi.createRouteDefinition({from: clone});
+    //         this.addStep(route, '', 0)
+    //     } else if (clipboardSteps.length === 1 && clipboardSteps[0]?.dslName === 'RouteDefinition') {
+    //         const clone = CamelUtil.cloneStep(clipboardSteps[0], true);
+    //         this.addStep(clone, '', 0)
+    //     } else if (selectedUuids.length === 1) {
+    //         const targetMeta = CamelDefinitionApiExt.findElementMetaInIntegration(integration, selectedUuids[0]);
+    //         clipboardSteps.reverse().forEach(clipboardStep => {
+    //             if (clipboardStep && targetMeta.parentUuid) {
+    //                 const clone = CamelUtil.cloneStep(clipboardStep, true);
+    //                 this.addStep(clone, targetMeta.parentUuid, targetMeta.position);
+    //             }
+    //         })
+    //     }
+    // }
+    //
+    // function  onCommand = (command: Command) => {
+    //     switch (command.command){
+    //         case "downloadImage": this.integrationImageDownload()
+    //     }
+    // }
+    //
+    // function onPropertyUpdate = debounce((element: CamelElement, newRoute?: RouteToCreate) => {
+    //     if (newRoute) {
+    //         let i = CamelDefinitionApiExt.updateIntegrationRouteElement(this.routeDesigner.state.integration, element);
+    //         const f = CamelDefinitionApi.createFromDefinition({uri: newRoute.componentName, parameters: {name: newRoute.name}});
+    //         const r = CamelDefinitionApi.createRouteDefinition({from: f, id: newRoute.name})
+    //         i = CamelDefinitionApiExt.addStepToIntegration(i, r, '');
+    //         const clone = CamelUtil.cloneIntegration(i);
+    //         this.routeDesigner.setState(prevState => ({
+    //             integration: clone,
+    //             key: Math.random().toString(),
+    //             showSelector: false,
+    //             selectedStep: element,
+    //             propertyOnly: false,
+    //             selectedUuids: [element.uuid]
+    //         }));
+    //     } else {
+    //         const clone = CamelUtil.cloneIntegration(this.routeDesigner.state.integration);
+    //         const i = CamelDefinitionApiExt.updateIntegrationRouteElement(clone, element);
+    //         this.routeDesigner.setState({integration: i, propertyOnly: true, key: Math.random().toString()});
+    //     }
+    // }, 300, {leading: true})
+    //
+    const onShowDeleteConfirmation = (id: string) => {
+        let message: string;
+        const uuidsToDelete:string [] = [id];
+        let ce: CamelElement;
+        ce = CamelDefinitionApiExt.findElementInIntegration(integration, id)!;
+        if (ce.dslName === 'FromDefinition') { // Get the RouteDefinition for this.routeDesigner.  Use its uuid.
+            let flows = integration.spec.flows!;
+            for (let i = 0; i < flows.length; i++) {
+                if (flows[i].dslName === 'RouteDefinition') {
+                    let routeDefinition: RouteDefinition = flows[i];
+                    if (routeDefinition.from.uuid === id) {
+                        uuidsToDelete.push(routeDefinition.uuid);
+                        break;
+                    }
+                }
+            }
+            message = 'Deleting the first element will delete the entire route!';
+        } else if (ce.dslName === 'RouteDefinition') {
+            message = 'Delete route?';
+        } else if (ce.dslName === 'RouteConfigurationDefinition') {
+            message = 'Delete route configuration?';
+        } else {
+            message = 'Delete element from route?';
+        }
+        setShowSelector(false);
+        setShowDeleteConfirmation(false);
+        setDeleteMessage(message);
+        setSelectedUuids(uuidsToDelete);
+    }
+
+    const deleteElement = () =>  {
+        selectedUuids.forEach(uuidToDelete => {
+            const i = CamelDefinitionApiExt.deleteStepFromIntegration(integration, uuidToDelete);
+            setIntegration(i);
+            setShowSelector(false);
+            setShowDeleteConfirmation(false);
+            setDeleteMessage('');
+            setSelectedStep(undefined);
+            setPropertyOnly(false);
+            setSelectedUuids([uuidToDelete]);
+
+            const el = new CamelElement("");
+            el.uuid = uuidToDelete;
+            EventBus.sendPosition("delete", el, undefined, new DOMRect(), new DOMRect(), 0);
+        });
+    }
+
+    const selectElement = (element: CamelElement) =>  {
+        // const {shiftKeyPressed, selectedUuids, integration} = this.routeDesigner.state;
+        let canNotAdd: boolean = false;
+        if (shiftKeyPressed) {
+            const hasFrom = selectedUuids.map(e => CamelDefinitionApiExt.findElementInIntegration(integration, e)?.dslName === 'FromDefinition').filter(r => r).length > 0;
+            canNotAdd = hasFrom || (selectedUuids.length > 0 && element.dslName === 'FromDefinition');
+        }
+        const add = shiftKeyPressed && !selectedUuids.includes(element.uuid);
+        const remove = shiftKeyPressed && selectedUuids.includes(element.uuid);
+        const i = CamelDisplayUtil.setIntegrationVisibility(integration, element.uuid);
+
+        if (remove) {
+            const index = selectedUuids.indexOf(element.uuid);
+            selectedUuids.splice(index, 1);
+        } else if (add && !canNotAdd) {
+            selectedUuids.push(element.uuid);
+        }
+        const uuid: string = selectedUuids.includes(element.uuid) ? element.uuid : selectedUuids.at(0) || '';
+        const selectedElement = shiftKeyPressed ? CamelDefinitionApiExt.findElementInIntegration(integration, uuid) : element;
+
+        setIntegration(i);
+        setSelectedStep(selectedElement);
+        setShowSelector(false);
+        setSelectedUuids(shiftKeyPressed ? [...selectedUuids] : [element.uuid])
+    }
+    //
+    // function unselectElement = (evt: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
+    //     if ((evt.target as any).dataset.click === 'FLOWS') {
+    //         evt.stopPropagation()
+    //         const i = CamelDisplayUtil.setIntegrationVisibility(this.routeDesigner.state.integration, undefined);
+    //         this.routeDesigner.setState(prevState => ({
+    //             integration: i,
+    //             selectedStep: undefined,
+    //             showSelector: false,
+    //             selectedPosition: undefined,
+    //             selectedUuids: [],
+    //         }));
+    //     }
+    // }
+    //
+    const openSelector = (parentId: string | undefined, parentDsl: string | undefined, showSteps: boolean = true, position?: number | undefined, selectorTabIndex?: string | number) => {
+        setShowSelector(true);
+        setParentId(parentId || '');
+        setParentDsl(parentDsl);
+        setShowSteps(showSteps);
+        setSelectedPosition(position);
+        setSelectorTabIndex(selectorTabIndex);
+    }
+    //
+    // function closeDslSelector = () => {
+    //     this.routeDesigner.setState({showSelector: false})
+    // }
+    //
+    const onDslSelect = (dsl: DslMetaModel, parentId: string, position?: number | undefined) => {
+        switch (dsl.dsl) {
+            case 'FromDefinition' :
+                const route = CamelDefinitionApi.createRouteDefinition({from: new FromDefinition({uri: dsl.uri})});
+                addStep(route, parentId, position)
+                break;
+            case 'ToDefinition' :
+                const to = CamelDefinitionApi.createStep(dsl.dsl, {uri: dsl.uri});
+                addStep(to, parentId, position)
+                break;
+            case 'ToDynamicDefinition' :
+                const toD = CamelDefinitionApi.createStep(dsl.dsl, {uri: dsl.uri});
+                addStep(toD, parentId, position)
+                break;
+            case 'KameletDefinition' :
+                const kamelet = CamelDefinitionApi.createStep(dsl.dsl, {name: dsl.name});
+                addStep(kamelet, parentId, position)
+                break;
+            default:
+                const step = CamelDefinitionApi.createStep(dsl.dsl, undefined);
+                const augmentedStep = setDslDefaults(step);
+                addStep(augmentedStep, parentId, position)
+                break;
+        }
+    }
+    //
+    function setDslDefaults(step: CamelElement): CamelElement {
+        if (step.dslName === 'LogDefinition') {
+            // eslint-disable-next-line no-template-curly-in-string
+            (step as LogDefinition).message = "${body}";
+        }
+        if (step.dslName === 'ChoiceDefinition') {
+            (step as ChoiceDefinition).when?.push(CamelDefinitionApi.createStep('WhenDefinition', undefined));
+            (step as ChoiceDefinition).otherwise = CamelDefinitionApi.createStep('OtherwiseDefinition', undefined);
+        }
+        return step;
+    }
+
+    const createRouteConfiguration = () => {
+        const clone = CamelUtil.cloneIntegration(integration);
+        const routeConfiguration = new RouteConfigurationDefinition();
+        const i = CamelDefinitionApiExt.addRouteConfigurationToIntegration(clone, routeConfiguration);
+        setIntegration(i);
+        setPropertyOnly(false);
+        setSelectedStep(routeConfiguration);
+        setSelectedUuids([routeConfiguration.uuid]);
+    }
+
+    const addStep = (step: CamelElement, parentId: string, position?: number | undefined) => {
+        const i = CamelDefinitionApiExt.addStepToIntegration(integration, step, parentId, position);
+        const clone = CamelUtil.cloneIntegration(i);
+        EventBus.sendPosition("clean", step, undefined, new DOMRect(), new DOMRect(), 0);
+        const selectedStep = step.dslName === 'RouteDefinition' ? (step as RouteDefinition).from  : step;
+        setIntegration(clone);
+        setShowSelector(false);
+        setSelectedStep(selectedStep);
+        setPropertyOnly(false);
+        setSelectedUuids([selectedStep.uuid]);
+    }
+    //
+    // function onIntegrationUpdate = (i: Integration) => {
+    //     this.routeDesigner.setState({integration: i, propertyOnly: false, showSelector: false, key: Math.random().toString()});
+    // }
+    //
+    const moveElement = (source: string, target: string, asChild: boolean) => {
+        const i = CamelDefinitionApiExt.moveRouteElement(integration, source, target, asChild);
+        const clone = CamelUtil.cloneIntegration(i);
+        const selectedStep = CamelDefinitionApiExt.findElementInIntegration(clone, source);
+
+        setIntegration(clone);
+        setShowSelector(false);
+        setSelectedStep(selectedStep);
+        setPropertyOnly(false);
+        setSelectedUuids([source]);
+    }
+    //
+
+    //
+    // function downloadIntegrationImage(dataUrl: string) {
+    //     const a = document.createElement('a');
+    //     a.setAttribute('download', 'karavan-routes.png');
+    //     a.setAttribute('href', dataUrl);
+    //     a.click();
+    // }
+    //
+    // function  integrationImageDownloadFilter = (node: HTMLElement) => {
+    //     const exclusionClasses = ['add-flow'];
+    //     return !exclusionClasses.some(classname => {
+    //         return node.classList === undefined ? false : node.classList.contains(classname);
+    //     });
+    // }
+    //
+    // function integrationImageDownload() {
+    //     if (this.routeDesigner.state.printerRef.current === null) {
+    //         return
+    //     }
+    //     toPng(this.routeDesigner.state.printerRef.current, {
+    //         style: {overflow: 'hidden'}, cacheBust: true, filter: this.integrationImageDownloadFilter,
+    //         height: this.routeDesigner.state.height, width: this.routeDesigner.state.width, backgroundColor: this.routeDesigner.props.dark ? "black" : "white"
+    //     }).then(v => {
+    //         toPng(this.routeDesigner.state.printerRef.current, {
+    //             style: {overflow: 'hidden'}, cacheBust: true, filter: this.integrationImageDownloadFilter,
+    //             height: this.routeDesigner.state.height, width: this.routeDesigner.state.width, backgroundColor: this.routeDesigner.props.dark ? "black" : "white"
+    //         }).then(this.downloadIntegrationImage);
+    //     })
+    // }
+
+    return { deleteElement, selectElement, moveElement, onShowDeleteConfirmation, onDslSelect, openSelector, createRouteConfiguration}
+}
\ No newline at end of file
diff --git a/karavan-designer/src/index.tsx b/karavan-designer/src/index.tsx
index 53d84ae5..3219d305 100644
--- a/karavan-designer/src/index.tsx
+++ b/karavan-designer/src/index.tsx
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React from 'react';
+import React, {StrictMode} from 'react';
 import "./index.css";
 import "@patternfly/patternfly/patternfly.css";
 import App from "./App";
@@ -22,4 +22,8 @@ import {createRoot} from "react-dom/client";
 
 const container = document.getElementById('root');
 const root = createRoot(container!);
-root.render(<App/>);
\ No newline at end of file
+root.render(
+    <StrictMode>
+        <App />
+    </StrictMode>
+);
\ No newline at end of file


[camel-karavan] 02/03: Scroll works

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit a2c1b127d71ed51d471e1781d5e605fa7b80eea1
Author: Marat Gubaidullin <ma...@Marats-MacBook-Pro.local>
AuthorDate: Mon Aug 21 16:53:47 2023 -0400

    Scroll works
---
 karavan-designer/public/example/demo.camel.yaml    | 30 +++++++++
 karavan-designer/src/App.tsx                       | 33 +++++-----
 karavan-designer/src/DesignerPage.tsx              |  6 +-
 karavan-designer/src/designer/KaravanDesigner.tsx  | 13 ++--
 karavan-designer/src/designer/KaravanStore.ts      |  4 +-
 karavan-designer/src/designer/karavan.css          | 14 ++--
 .../src/designer/route/DslConnections.tsx          | 30 +++++----
 karavan-designer/src/designer/route/DslElement.tsx | 28 ++++++--
 .../src/designer/route/ElementResizeListener.tsx   | 75 ----------------------
 .../src/designer/route/RouteDesigner.tsx           | 50 ++++++++-------
 .../route/useResizeObserver.tsx}                   | 42 ++++++++----
 karavan-designer/src/index.css                     |  6 --
 karavan-designer/src/index.tsx                     |  4 +-
 13 files changed, 167 insertions(+), 168 deletions(-)

diff --git a/karavan-designer/public/example/demo.camel.yaml b/karavan-designer/public/example/demo.camel.yaml
index 2db92f8d..878ed244 100644
--- a/karavan-designer/public/example/demo.camel.yaml
+++ b/karavan-designer/public/example/demo.camel.yaml
@@ -39,3 +39,33 @@
               - routingSlip:
                   expression: {}
                   id: routingSlip-a85a
+- route:
+    id: route-178a
+    from:
+      uri: kamelet:aws-cloudtrail-source
+      id: from-3e7d
+      steps:
+        - multicast:
+            id: multicast-eef7
+            steps:
+              - bean:
+                  id: bean-a5ef
+              - aggregate:
+                  id: aggregate-f5d8
+              - aggregate:
+                  id: aggregate-b9e7
+              - aggregate:
+                  id: aggregate-5eb8
+              - aggregate:
+                  id: aggregate-c57e
+              - aggregate:
+                  id: aggregate-1cd4
+              - bean:
+                  id: bean-72a1
+              - choice:
+                  when:
+                    - expression: {}
+                      id: when-a56b
+                  otherwise:
+                    id: otherwise-9f31
+                  id: choice-1905
diff --git a/karavan-designer/src/App.tsx b/karavan-designer/src/App.tsx
index 3e98dc50..4e751d60 100644
--- a/karavan-designer/src/App.tsx
+++ b/karavan-designer/src/App.tsx
@@ -18,8 +18,8 @@ import * as React from "react";
 import {
     Alert,
     AlertActionCloseButton, AlertGroup,
-    Bullseye, Button, Divider, Flex, FlexItem,
-    Page, Spinner, Tooltip,
+    Bullseye, Button, Divider, Flex, FlexItem, Masthead, MastheadBrand, MastheadContent, MastheadMain, MastheadToggle,
+    Page, PageSidebar, PageSidebarBody, PageToggleButton, Spinner, Tooltip,
 } from "@patternfly/react-core";
 import {KameletApi} from "karavan-core/lib/api/KameletApi";
 import {ComponentApi} from "karavan-core/lib/api/ComponentApi";
@@ -145,7 +145,7 @@ class App extends React.Component<Props, State> {
             new MenuItem("designer", "Designer", <BlueprintIcon/>),
             new MenuItem("knowledgebase", "Knowledgebase", <KnowledgebaseIcon/>),
         ]
-        return (<Flex className="nav-buttons" direction={{default: "column"}} style={{height: "100%"}}
+        return (<Flex className="nav-buttons" direction={{default: "column"}} //style={{height: "100%"}}
                       spaceItems={{default: "spaceItemsNone"}}>
             <FlexItem alignSelf={{default: "alignSelfCenter"}}>
                 <Tooltip className="logo-tooltip" content={"Apache Camel Karavan"}
@@ -188,10 +188,21 @@ class App extends React.Component<Props, State> {
         }
     }
 
+    getHeader = () => (
+        <Masthead>
+        </Masthead>
+    );
+
+    getSidebar = () => (
+        <PageSidebar isSidebarOpen={true} id="fill-sidebar">
+            <PageSidebarBody>Navigation</PageSidebarBody>
+        </PageSidebar>
+    );
+
     public render() {
         const {loaded} = this.state;
         return (
-            <Page className="karavan">
+            <Page className="karavan" header={this.getHeader()} sidebar={this.pageNav()}>
                 <AlertGroup isToast isLiveRegion>
                     {this.state.alerts.map((e: ToastMessage) => (
                         // @ts-ignore
@@ -202,18 +213,8 @@ class App extends React.Component<Props, State> {
                         </Alert>
                     ))}
                 </AlertGroup>
-                <>
-                    <Flex direction={{default: "row"}} style={{width: "100%", height: "100%"}}
-                          alignItems={{default: "alignItemsStretch"}} spaceItems={{default: 'spaceItemsNone'}}>
-                        <FlexItem>
-                            {this.pageNav()}
-                        </FlexItem>
-                        <FlexItem flex={{default: "flex_2"}} style={{height: "100%"}}>
-                            {loaded !== true && this.getSpinner()}
-                            {loaded === true && this.getPage()}
-                        </FlexItem>
-                    </Flex>
-                </>
+                {loaded !== true && this.getSpinner()}
+                {loaded === true && this.getPage()}
             </Page>
         )
     }
diff --git a/karavan-designer/src/DesignerPage.tsx b/karavan-designer/src/DesignerPage.tsx
index 9bcc0a2e..5d3ed033 100644
--- a/karavan-designer/src/DesignerPage.tsx
+++ b/karavan-designer/src/DesignerPage.tsx
@@ -105,8 +105,8 @@ export class DesignerPage extends React.Component<Props, State> {
     render() {
         const {mode} = this.state;
         return (
-            <PageSection className="kamelet-section designer-page" padding={{default: 'noPadding'}}>
-                <PageSection className="tools-section" padding={{default: 'noPadding'}}
+            <PageSection className="designer-page" padding={{default: 'noPadding'}}>
+                <div className="tools-section" //padding={{default: 'noPadding'}}
                              style={{backgroundColor:"transparent", paddingLeft: "var(--pf-v5-c-page__main-section--PaddingLeft)"}}>
                     <Flex className="tools" justifyContent={{default: 'justifyContentSpaceBetween'}}>
                         <FlexItem>
@@ -143,7 +143,7 @@ export class DesignerPage extends React.Component<Props, State> {
                             </Toolbar>
                         </FlexItem>
                     </Flex>
-                </PageSection>
+                </div>
                 {mode === 'design' && this.getDesigner()}
                 {mode === 'code'  && this.getEditor()}
             </PageSection>
diff --git a/karavan-designer/src/designer/KaravanDesigner.tsx b/karavan-designer/src/designer/KaravanDesigner.tsx
index b65a66f1..5f25fe91 100644
--- a/karavan-designer/src/designer/KaravanDesigner.tsx
+++ b/karavan-designer/src/designer/KaravanDesigner.tsx
@@ -72,7 +72,6 @@ export const KaravanDesigner = (props: Props) => {
         [state.integration, state.setIntegration], shallow )
 
     useEffect(() => {
-        console.log("useEffect");
         setIntegration(makeIntegration(props.yaml, props.filename));
     }, []);
 
@@ -122,11 +121,13 @@ export const KaravanDesigner = (props: Props) => {
 
     return (
         <PageSection variant={props.dark ? PageSectionVariants.darker : PageSectionVariants.light} className="page" isFilled padding={{default: 'noPadding'}}>
-            <Tabs className="main-tabs" activeKey={tab} onSelect={(event, tabIndex) => setTab(tabIndex.toString())} style={{width: "100%"}}>
-                <Tab eventKey='routes' title={getTab("Routes", "Integration flows", "routes")}></Tab>
-                <Tab eventKey='rest' title={getTab("REST", "REST services", "rest")}></Tab>
-                <Tab eventKey='beans' title={getTab("Beans", "Beans Configuration", "beans")}></Tab>
-            </Tabs>
+            <div>
+                <Tabs className="main-tabs" activeKey={tab} onSelect={(event, tabIndex) => setTab(tabIndex.toString())} style={{width: "100%"}}>
+                    <Tab eventKey='routes' title={getTab("Routes", "Integration flows", "routes")}></Tab>
+                    <Tab eventKey='rest' title={getTab("REST", "REST services", "rest")}></Tab>
+                    <Tab eventKey='beans' title={getTab("Beans", "Beans Configuration", "beans")}></Tab>
+                </Tabs>
+            </div>
             {tab === 'routes' && <RouteDesigner
                 // integration={integration}
                                                 // onSave={(integration, propertyOnly) => save(integration, propertyOnly)}
diff --git a/karavan-designer/src/designer/KaravanStore.ts b/karavan-designer/src/designer/KaravanStore.ts
index 4e710e61..a28f7f21 100644
--- a/karavan-designer/src/designer/KaravanStore.ts
+++ b/karavan-designer/src/designer/KaravanStore.ts
@@ -117,8 +117,8 @@ export const useDesignerStore = createWithEqualityFn<DesignerState>((set) => ({
     setClipboardSteps: (clipboardSteps: string[]) => {
         set({clipboardSteps: clipboardSteps})
     },
-    width: 1000,
-    height: 1000,
+    width: 100,
+    height: 100,
     top: 0,
     left: 0,
     setPosition: (width: number, height: number, top: number, left: number) => {
diff --git a/karavan-designer/src/designer/karavan.css b/karavan-designer/src/designer/karavan.css
index d0cfcc36..71192fc2 100644
--- a/karavan-designer/src/designer/karavan.css
+++ b/karavan-designer/src/designer/karavan.css
@@ -231,10 +231,13 @@
     flex-direction: column;
 }
 
+.karavan main {
+    overflow: hidden;
+}
+
 /*DSL*/
 .karavan .dsl-page {
-    flex: 1;
-    overflow: auto;
+    height: 100%;
 }
 
 .karavan .dsl-page .dsl-page-columns {
@@ -551,8 +554,8 @@
 
 .karavan .dsl-page .flows {
     width: 100%;
-    position: relative;
-    margin-bottom: 80px;
+    position: absolute;
+    /*margin-bottom: 80px;*/
 }
 
 .karavan .dsl-page .flows .add-flow {
@@ -1384,6 +1387,9 @@
 
 .karavan .designer-page {
     background-color: white;
+    display: flex;
+    flex-direction: column;
+    height: 100%;
 }
 
 .karavan .designer-page .project-page-section {
diff --git a/karavan-designer/src/designer/route/DslConnections.tsx b/karavan-designer/src/designer/route/DslConnections.tsx
index db7cd2dd..7a618057 100644
--- a/karavan-designer/src/designer/route/DslConnections.tsx
+++ b/karavan-designer/src/designer/route/DslConnections.tsx
@@ -33,16 +33,17 @@ export const DslConnections = () => {
         [s.width, s.height, s.top, s.left])
     const [steps, setSteps] = useState<Map<string, DslPosition>>(new Map<string, DslPosition>());
 
-    useEffect(() => {
-        console.log("DslConnections Start", width, height, top, left);
-        const sub = EventBus.onPosition()?.subscribe((evt: DslPosition) => setPosition(evt));
-        return () => {
-            console.log("DslConnections Stop");
-            sub?.unsubscribe();
-        };
-    }, [width, height, top, left]);
+    // useEffect(() => {
+    //     console.log("DslConnections Start", width, height, top, left);
+    //     const sub = EventBus.onPosition()?.subscribe((evt: DslPosition) => setPosition(evt));
+    //     return () => {
+    //         console.log("DslConnections Stop");
+    //         sub?.unsubscribe();
+    //     };
+    // }, [width, height, top, left]);
 
     function setPosition(evt: DslPosition) {
+        console.log("setPosition", evt);
         if (evt.command === "add") {
             setSteps(prevSteps => {
                 prevSteps.set(evt.step.uuid, evt);
@@ -442,8 +443,8 @@ export const DslConnections = () => {
         const stepsArray = Array.from(steps.values());
         return (
             <svg
-                style={{width: width, height: height, position: "absolute", left: 0, top: 0}}
-                viewBox={"0 0 " + width + " " + height}>
+                style={{width: width - 5, height: height - 5, position: "absolute", left: 0, top: 0}}
+                viewBox={"0 0 " + (width - 5) + " " + (height -5)}>
                 <defs>
                     <marker id="arrowhead" markerWidth="9" markerHeight="6" refX="0" refY="3" orient="auto" className="arrow">
                         <polygon points="0 0, 9 3, 0 6"/>
@@ -458,11 +459,12 @@ export const DslConnections = () => {
         )
     }
 
+    console.log("RENDER CONNECTION")
     return (
-        <div className="connections" style={{width: width, height: height, marginTop: "0px"}}>
-            {getSvg()}
-            {getIncomings().map(p => getIncomingIcons(p))}
-            {getOutgoings().map(p => getOutgoingIcons(p))}
+        <div style={{position: "absolute", width: width + 5, height: height + 80, top: 0, left: 0, background: "red"}}>
+            {/*{getSvg()}*/}
+            {/*{getIncomings().map(p => getIncomingIcons(p))}*/}
+            {/*{getOutgoings().map(p => getOutgoingIcons(p))}*/}
         </div>
     )
 }
diff --git a/karavan-designer/src/designer/route/DslElement.tsx b/karavan-designer/src/designer/route/DslElement.tsx
index 2b498d02..212be87c 100644
--- a/karavan-designer/src/designer/route/DslElement.tsx
+++ b/karavan-designer/src/designer/route/DslElement.tsx
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React, {createRef, CSSProperties, useState} from 'react';
+import React, {createRef, CSSProperties, useEffect, useRef, useState} from 'react';
 import {
     Button,
     Flex,
@@ -67,6 +67,24 @@ export const DslElement = (props: Props) => {
     const [isDraggedOver, setIsDraggedOver] = useState<boolean>(false);
     const [moveElements, setMoveElements] = useState<[string | undefined, string | undefined]>([undefined, undefined]);
 
+    const elementRef = useRef(null);
+    const [elementPosition, setElementPosition] = useState({ x: 0, y: 0 });
+
+    useEffect(() => {
+        // function handleResize() {
+        //     if (elementRef && elementRef.current !== undefined) {
+        //         const current: any = (elementRef.current as any);
+        //         const x = current.offsetLeft;
+        //         const y = current.offsetTop;
+        //         setElementPosition({x, y});
+        //         // console.log("setElementPosition", {x, y})
+        //     }
+        // }
+        //
+        // handleResize(); // initial call to get position of the element on mount
+        // window.addEventListener("resize", handleResize);
+        // return () => window.removeEventListener("resize", handleResize);
+    }, [elementRef]);
     //
     // componentDidUpdate = (prevProps: Readonly<Props>, prevState: Readonly<State>, snapshot?: any) => {
     //     if (prevselectedUuid !== props.selectedUuid) {
@@ -213,7 +231,7 @@ export const DslElement = (props: Props) => {
     }
 
     function sendPosition (el: HTMLDivElement | null, isSelected: boolean) {
-        // console.log("sendPosition", props.step)
+        console.log("sendPosition", props.step)
         const node = el;
         if (node && el) {
             const header = Array.from(node.childNodes.values()).filter((n: any) => n.classList.contains("header"))[0];
@@ -244,7 +262,8 @@ export const DslElement = (props: Props) => {
         return (
             <div className={headerClasses} style={getHeaderStyle()}>
                 {!['RouteConfigurationDefinition', 'RouteDefinition'].includes(props.step.dslName) &&
-                    <div ref={el => sendPosition(el, isSelected())}
+                    <div
+                        // ref={el => sendPosition(el, isSelected())}
                          className={"header-icon"}
                          style={isWide() ? {width: ""} : {}}>
                         {CamelUi.getIconForElement(step)}
@@ -439,7 +458,8 @@ export const DslElement = (props: Props) => {
     return (
         <div key={"root" + element.uuid}
              className={className}
-             ref={el => sendPosition(el, isSelected())}
+             ref={elementRef}
+             // ref={el => sendPosition(el, isSelected())}
              style={{
                  borderStyle: hasBorder() ? "dotted" : "none",
                  borderColor: isSelected() ? "var(--step-border-color-selected)" : "var(--step-border-color)",
diff --git a/karavan-designer/src/designer/route/ElementResizeListener.tsx b/karavan-designer/src/designer/route/ElementResizeListener.tsx
deleted file mode 100644
index 4ef53c64..00000000
--- a/karavan-designer/src/designer/route/ElementResizeListener.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * 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 {RefObject, useCallback, useEffect, useRef} from "react";
-
-interface Props {
-    onResize: (event: Event) => void;
-};
-export const ElementResizeListener: React.FC<Props> = ({ onResize }) => {
-    const rafRef = useRef(0);
-    const objectRef: RefObject<HTMLObjectElement> = useRef(null);
-    const onResizeRef = useRef(onResize);
-
-    onResizeRef.current = onResize;
-
-    const _onResize = useCallback((e: Event) => {
-        if (rafRef.current) {
-            cancelAnimationFrame(rafRef.current);
-        }
-        rafRef.current = requestAnimationFrame(() => {
-            onResizeRef.current(e);
-        });
-    }, []);
-
-    const onLoad = useCallback(() => {
-        const obj = objectRef.current;
-        if (obj && obj.contentDocument && obj.contentDocument.defaultView) {
-            obj.contentDocument.defaultView.addEventListener('resize', _onResize);
-        }
-    }, []);
-
-    useEffect(() => {
-        return () => {
-            const obj = objectRef.current;
-            if (obj && obj.contentDocument && obj.contentDocument.defaultView) {
-                obj.contentDocument.defaultView.removeEventListener('resize', _onResize);
-            }
-        }
-    }, []);
-
-    return (
-        <object
-            aria-label="object"
-            onLoad={onLoad}
-            ref={objectRef} tabIndex={-1}
-            type={'text/html'}
-            data={'about:blank'}
-            title={''}
-            style={{
-                position: 'absolute',
-                top: 0,
-                left: 0,
-                height: '100%',
-                width: '100%',
-                pointerEvents: 'none',
-                zIndex: -1,
-                opacity: 0,
-            }}
-        />
-    )
-}
diff --git a/karavan-designer/src/designer/route/RouteDesigner.tsx b/karavan-designer/src/designer/route/RouteDesigner.tsx
index 101309bb..4f460933 100644
--- a/karavan-designer/src/designer/route/RouteDesigner.tsx
+++ b/karavan-designer/src/designer/route/RouteDesigner.tsx
@@ -38,7 +38,7 @@ import {shallow} from "zustand/shallow";
 import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
 import {RouteConfigurationDefinition} from "karavan-core/lib/model/CamelDefinition";
 import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
-import {ElementResizeListener} from "./ElementResizeListener";
+import useResizeObserver from "./useResizeObserver";
 
 interface Props {
     // onSave?: (integration: Integration, propertyOnly: boolean) => void
@@ -48,25 +48,28 @@ interface Props {
 
 export const RouteDesigner = (props: Props) => {
 
-    const ref = React.createRef();
     const printerRef = React.createRef()
     const contentRef: React.RefObject<HTMLDivElement> = useRef(null);
     const flowRef: React.RefObject<HTMLDivElement> = useRef(null);
 
-    const { deleteElement, selectElement, moveElement, onShowDeleteConfirmation, onDslSelect, openSelector, createRouteConfiguration} = useRouteDesignerHook();
+    const {
+        deleteElement,
+        selectElement,
+        moveElement,
+        onShowDeleteConfirmation,
+        onDslSelect,
+        openSelector,
+        createRouteConfiguration
+    } = useRouteDesignerHook();
 
     const [integration, setIntegration] = useIntegrationStore((state) => [state.integration, state.setIntegration], shallow)
-    const [showSelector, showDeleteConfirmation, propertyOnly, showSteps,deleteMessage,parentId, selectedUuids,clipboardSteps, parentDsl,selectedPosition,selectedStep,selectorTabIndex,
-        setShowSelector, setShowDeleteConfirmation,setPropertyOnly,setShowSteps,setDeleteMessage, setParentId, setSelectedUuids, setClipboardSteps,setPosition,
+    const [showSelector, showDeleteConfirmation, propertyOnly, showSteps, deleteMessage, parentId, selectedUuids, clipboardSteps, parentDsl, selectedPosition, selectedStep, selectorTabIndex,
+        setShowSelector, setShowDeleteConfirmation, setPropertyOnly, setShowSteps, setDeleteMessage, setParentId, setSelectedUuids, setClipboardSteps, setPosition,
         width, height, top, left] = useDesignerStore((s) =>
-        [s.showSelector, s.showDeleteConfirmation, s.propertyOnly, s.showSteps,s.deleteMessage,s.parentId, s.selectedUuids,s.clipboardSteps, s.parentDsl, s.selectedPosition, s.selectedStep,s.selectorTabIndex,
-            s.setShowSelector, s.setShowDeleteConfirmation,s.setPropertyOnly,s.setShowSteps,s.setDeleteMessage, s.setParentId, s.setSelectedUuids, s.setClipboardSteps, s.setPosition,
+        [s.showSelector, s.showDeleteConfirmation, s.propertyOnly, s.showSteps, s.deleteMessage, s.parentId, s.selectedUuids, s.clipboardSteps, s.parentDsl, s.selectedPosition, s.selectedStep, s.selectorTabIndex,
+            s.setShowSelector, s.setShowDeleteConfirmation, s.setPropertyOnly, s.setShowSteps, s.setDeleteMessage, s.setParentId, s.setSelectedUuids, s.setClipboardSteps, s.setPosition,
             s.width, s.height, s.top, s.left], shallow)
 
-    useEffect(() => {
-        adaptResize();
-    })
-
     // function componentDidMount() {
     //     logic.componentDidMount();
     // }
@@ -150,28 +153,28 @@ export const RouteDesigner = (props: Props) => {
     //     }
     // }
 
-    const adaptResize = useCallback(() => {
-        if (contentRef.current && flowRef.current) {
-            const el = contentRef.current.getBoundingClientRect();
-            // flowRef.current.textContent = `width: ${elmRect.width}`;
-            console.log("elmRect", el)
+    const onResize = useCallback((target: HTMLDivElement) => {
+        const el = target.getBoundingClientRect();
+        if (width !== el.width || height !== el.height || top !== el.top || left !== el.left) {
             setPosition(el.width, el.height, el.top, el.left)
+            console.log("elmRect", el)
         }
     }, []);
 
+    const ref = useResizeObserver(onResize);
+
+    console.log("RENDER ROUTE_DESIGNER")
 
     function getGraph() {
         const routes = CamelUi.getRoutes(integration);
         const routeConfigurations = CamelUi.getRouteConfigurations(integration);
         return (
-            <div className="graph" ref={contentRef}>
+            <div className="graph">
                 <DslConnections/>
-                <ElementResizeListener onResize={adaptResize}/>
                 <div id="flows" className="flows" data-click="FLOWS" onClick={event => {
                     // logic.unselectElement(event)
                 }}
-                     // ref={el => onResizePage(el)}
-                     ref={flowRef}
+                     ref={ref}
                 >
                     {routeConfigurations?.map((routeConfiguration, index: number) => (
                         <DslElement key={routeConfiguration.uuid}
@@ -209,17 +212,18 @@ export const RouteDesigner = (props: Props) => {
             </div>)
     }
 
+    const hasFlows = integration?.spec?.flows?.length && integration?.spec?.flows?.length > 0;
     return (
-        <PageSection className="dsl-page" isFilled padding={{default: 'noPadding'}}>
+        <div className="dsl-page">
             <div className="dsl-page-columns">
                 <Drawer isExpanded isInline>
                     <DrawerContent panelContent={getPropertiesPanel()}>
-                        <DrawerContentBody>{getGraph()}</DrawerContentBody>
+                        <DrawerContentBody>{hasFlows && getGraph()}</DrawerContentBody>
                     </DrawerContent>
                 </Drawer>
             </div>
             {getSelectorModal()}
             {getDeleteConfirmation()}
-        </PageSection>
+        </div>
     );
 }
\ No newline at end of file
diff --git a/karavan-designer/src/index.tsx b/karavan-designer/src/designer/route/useResizeObserver.tsx
similarity index 56%
copy from karavan-designer/src/index.tsx
copy to karavan-designer/src/designer/route/useResizeObserver.tsx
index 3219d305..93141b20 100644
--- a/karavan-designer/src/index.tsx
+++ b/karavan-designer/src/designer/route/useResizeObserver.tsx
@@ -14,16 +14,32 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React, {StrictMode} from 'react';
-import "./index.css";
-import "@patternfly/patternfly/patternfly.css";
-import App from "./App";
-import {createRoot} from "react-dom/client";
-
-const container = document.getElementById('root');
-const root = createRoot(container!);
-root.render(
-    <StrictMode>
-        <App />
-    </StrictMode>
-);
\ No newline at end of file
+
+import { useLayoutEffect, useRef } from 'react';
+
+function useResizeObserver<T extends HTMLElement>(
+    callback: (target: T, entry: ResizeObserverEntry) => void
+) {
+    const ref = useRef<T>(null)
+
+    useLayoutEffect(() => {
+        const element = ref?.current;
+
+        if (!element) {
+            return;
+        }
+
+        const observer = new ResizeObserver((entries) => {
+            callback(element, entries[0]);
+        });
+
+        observer.observe(element);
+        return () => {
+            observer.disconnect();
+        };
+    }, [callback, ref]);
+
+    return ref
+}
+
+export default useResizeObserver;
\ No newline at end of file
diff --git a/karavan-designer/src/index.css b/karavan-designer/src/index.css
index d2ffcae4..a7341d7a 100644
--- a/karavan-designer/src/index.css
+++ b/karavan-designer/src/index.css
@@ -8,14 +8,8 @@ body,
 #root {
   margin: 0;
   height: 100%;
-}
-
-#root {
   display: flex;
   flex-direction: column;
-}
-
-.karavan .pf-v5-c-page__main {
   overflow: hidden;
 }
 
diff --git a/karavan-designer/src/index.tsx b/karavan-designer/src/index.tsx
index 3219d305..5a495994 100644
--- a/karavan-designer/src/index.tsx
+++ b/karavan-designer/src/index.tsx
@@ -23,7 +23,7 @@ import {createRoot} from "react-dom/client";
 const container = document.getElementById('root');
 const root = createRoot(container!);
 root.render(
-    <StrictMode>
+    // <StrictMode>
         <App />
-    </StrictMode>
+    // </StrictMode>
 );
\ No newline at end of file