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

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


The following commit(s) were added to refs/heads/main by this push:
     new 502d0747 Fix #1302
502d0747 is described below

commit 502d07470aabff1565b4607eb8127e7ad3f71520
Author: Marat Gubaidullin <ma...@talismancloud.io>
AuthorDate: Thu Feb 13 19:00:20 2025 -0500

    Fix #1302
---
 .../src/designer/route/element/DslElement.css      | 14 +++++++++++
 .../designer/route/element/DslElementHeader.tsx    | 28 ++++++++++++++++++----
 .../src/designer/route/useRouteDesignerHook.tsx    | 10 +++++++-
 .../main/webui/src/designer/utils/ElementIcon.css  |  7 ++++++
 .../main/webui/src/designer/utils/ElementIcons.tsx | 21 ++++++++++++++++
 karavan-designer/public/example/demo.camel.yaml    |  9 ++++---
 karavan-designer/src/App.tsx                       |  4 ++--
 .../src/designer/route/element/DslElement.css      | 14 +++++++++++
 .../designer/route/element/DslElementHeader.tsx    | 28 ++++++++++++++++++----
 .../src/designer/route/useRouteDesignerHook.tsx    | 10 +++++++-
 .../src/designer/utils/ElementIcon.css             |  7 ++++++
 .../src/designer/utils/ElementIcons.tsx            | 21 ++++++++++++++++
 .../src/designer/route/element/DslElement.css      | 14 +++++++++++
 .../designer/route/element/DslElementHeader.tsx    | 28 ++++++++++++++++++----
 .../src/designer/route/useRouteDesignerHook.tsx    | 10 +++++++-
 karavan-space/src/designer/utils/ElementIcon.css   |  7 ++++++
 karavan-space/src/designer/utils/ElementIcons.tsx  | 21 ++++++++++++++++
 17 files changed, 233 insertions(+), 20 deletions(-)

diff --git 
a/karavan-app/src/main/webui/src/designer/route/element/DslElement.css 
b/karavan-app/src/main/webui/src/designer/route/element/DslElement.css
index 791acdb3..5ab8ecfd 100644
--- a/karavan-app/src/main/webui/src/designer/route/element/DslElement.css
+++ b/karavan-app/src/main/webui/src/designer/route/element/DslElement.css
@@ -235,6 +235,20 @@
     z-index: 100;
 }
 
+.karavan .step-element .copy-element-button {
+    position: absolute;
+    bottom: 9px;
+    line-height: 1;
+    border: 0;
+    padding: 0;
+    margin: 0 0 0 -30px;
+    background: transparent;
+    color: var(--pf-v5-global--primary-color--100);
+    visibility: hidden;
+    z-index: 100;
+}
+
+.karavan .step-element .header:hover .copy-element-button,
 .karavan .step-element .header:hover .insert-element-button {
     visibility: visible;
 }
diff --git 
a/karavan-app/src/main/webui/src/designer/route/element/DslElementHeader.tsx 
b/karavan-app/src/main/webui/src/designer/route/element/DslElementHeader.tsx
index db181cff..90eb5985 100644
--- a/karavan-app/src/main/webui/src/designer/route/element/DslElementHeader.tsx
+++ b/karavan-app/src/main/webui/src/designer/route/element/DslElementHeader.tsx
@@ -26,7 +26,7 @@ import {CamelDisplayUtil} from 
"karavan-core/lib/api/CamelDisplayUtil";
 import {useDesignerStore, useIntegrationStore} from "../../DesignerStore";
 import {shallow} from "zustand/shallow";
 import {useRouteDesignerHook} from "../useRouteDesignerHook";
-import {AddElementIcon, DeleteElementIcon, InsertElementIcon} from 
"../../utils/ElementIcons";
+import {AddElementIcon, DeleteElementIcon, InsertElementIcon, CopyElementIcon} 
from "../../utils/ElementIcons";
 import { RouteConfigurationDefinition} from 
"karavan-core/lib/model/CamelDefinition";
 import {AutoStartupIcon, ErrorHandlerIcon} from "../../icons/OtherIcons";
 
@@ -50,7 +50,8 @@ export function DslElementHeader(props: Props) {
         openSelector,
         isKamelet,
         isSourceKamelet,
-        isActionKamelet
+        isActionKamelet,
+        copyPasteStep
     } = useRouteDesignerHook();
 
     const [integration] = useIntegrationStore((s) => [s.integration], shallow)
@@ -217,6 +218,7 @@ export function DslElementHeader(props: Props) {
             !['FromDefinition', 'RouteConfigurationDefinition', 
'RouteTemplateDefinition', 'RouteDefinition', 'CatchDefinition', 
'FinallyDefinition', 'WhenDefinition', 
'OtherwiseDefinition'].includes(step.dslName)
             && !inRouteConfiguration;
         const showDeleteButton = !('RouteDefinition' === step.dslName && 
'RouteTemplateDefinition' === parent?.dslName);
+        const showCopyButton = ['ToDefinition', 'ToDynamicDefinition', 
'PollDefinition'].includes(step.dslName);
         const headerClasses = getHeaderClasses();
         const childrenInfo = getChildrenInfo(step) || [];
         const hasWideChildrenElement = getHasWideChildrenElement(childrenInfo)
@@ -250,6 +252,7 @@ export function DslElementHeader(props: Props) {
                 {!isDebugging && showInsertButton && getInsertElementButton()}
                 {!isDebugging && showDeleteButton && getDeleteButton()}
                 {!isDebugging && showAddButton && getAddElementButton()}
+                {!isDebugging && showCopyButton && getCopyElementButton()}
             </div>
         )
     }
@@ -313,8 +316,7 @@ export function DslElementHeader(props: Props) {
 
     function getAddElementButton() {
         return (
-            <Tooltip position={"bottom"}
-                     content={<div>{"Add DSL element to " + 
CamelDisplayUtil.getTitle(step)}</div>}>
+            <Tooltip position={"bottom"} content={<div>{"Add DSL element to " 
+ CamelDisplayUtil.getTitle(step)}</div>}>
                 <button
                     type="button"
                     aria-label="Add"
@@ -325,6 +327,24 @@ export function DslElementHeader(props: Props) {
             </Tooltip>
         )
     }
+    function getCopyElementButton() {
+        return (
+            <Tooltip position={"left"} content={"Copy element"}>
+                <button
+                    type="button"
+                    aria-label="Copy"
+                    onClick={e => {
+                        e.stopPropagation();
+                        if (props.parent) {
+                            copyPasteStep(step, props.parent?.uuid, 
props.position)
+                        }
+                    }}
+                    className={"copy-element-button"}>
+                    <CopyElementIcon/>
+                </button>
+            </Tooltip>
+        )
+    }
 
     function getInsertElementButton() {
         return (
diff --git 
a/karavan-app/src/main/webui/src/designer/route/useRouteDesignerHook.tsx 
b/karavan-app/src/main/webui/src/designer/route/useRouteDesignerHook.tsx
index 62cda4bb..ac92bfe5 100644
--- a/karavan-app/src/main/webui/src/designer/route/useRouteDesignerHook.tsx
+++ b/karavan-app/src/main/webui/src/designer/route/useRouteDesignerHook.tsx
@@ -182,6 +182,14 @@ export function useRouteDesignerHook() {
         }
     }
 
+    function copyPasteStep(step: CamelElement, parentUuid: string, position: 
number): void {
+        if (step) {
+            const clone = CamelUtil.cloneStep(step, true);
+            (clone as any).id = (clone as any).stepName + "-" + 
clone.uuid.substring(0,4);
+            addStep(clone, parentUuid, position + 1);
+        }
+    }
+
     function copyToClipboard(): void {
         const steps: CamelElement[] = []
         selectedUuids.forEach(selectedUuid => {
@@ -397,6 +405,6 @@ export function useRouteDesignerHook() {
     return {
         deleteElement, selectElement, moveElement, onShowDeleteConfirmation, 
onDslSelect, openSelector,
         createRouteConfiguration, onCommand, handleKeyDown, handleKeyUp, 
unselectElement, isKamelet, isSourceKamelet,
-        isActionKamelet, isSinkKamelet, openSelectorToReplaceFrom, 
createRouteTemplate
+        isActionKamelet, isSinkKamelet, openSelectorToReplaceFrom, 
createRouteTemplate, copyPasteStep
     }
 }
\ No newline at end of file
diff --git a/karavan-app/src/main/webui/src/designer/utils/ElementIcon.css 
b/karavan-app/src/main/webui/src/designer/utils/ElementIcon.css
index f89c962b..ee7d87dc 100644
--- a/karavan-app/src/main/webui/src/designer/utils/ElementIcon.css
+++ b/karavan-app/src/main/webui/src/designer/utils/ElementIcon.css
@@ -24,6 +24,13 @@
     vertical-align: text-bottom;
 }
 
+.copy-button-icon {
+    width: 20px;
+    height: 20px;
+    background: transparent;
+    vertical-align: text-bottom;
+}
+
 .delete-button-icon {
     fill: var(--pf-v5-global--danger-color--100);
     width: 20px;
diff --git a/karavan-app/src/main/webui/src/designer/utils/ElementIcons.tsx 
b/karavan-app/src/main/webui/src/designer/utils/ElementIcons.tsx
index 2d44bc5e..63c3ee43 100644
--- a/karavan-app/src/main/webui/src/designer/utils/ElementIcons.tsx
+++ b/karavan-app/src/main/webui/src/designer/utils/ElementIcons.tsx
@@ -18,6 +18,27 @@
 import "./ElementIcon.css"
 import React from 'react'
 
+export function CopyElementIcon() {
+    return (
+        <svg
+            xmlns="http://www.w3.org/2000/svg";
+            className="copy-button-icon"
+            fill="none"
+            viewBox="0 0 27 27"
+        >
+            <path
+                strokeLinecap="round"
+                strokeLinejoin="round"
+                strokeWidth={2}
+                style={{
+                    stroke: "var(--pf-v5-global--primary-color--100)",
+                }}
+                d="M9.41 2.34a9.996 9.996 0 0 1 9.661 2.589c3.905 3.905 3.905 
10.237 0 14.142-3.905 3.905-10.237 3.905-14.142 0a9.996 9.996 0 0 
1-2.59-9.66M15 9v6m0 0H9m6 0L5 5"
+            />
+        </svg>
+    )
+}
+
 export function DeleteElementIcon() {
     return (
         <svg
diff --git a/karavan-designer/public/example/demo.camel.yaml 
b/karavan-designer/public/example/demo.camel.yaml
index cf7b8ed0..d3d24002 100644
--- a/karavan-designer/public/example/demo.camel.yaml
+++ b/karavan-designer/public/example/demo.camel.yaml
@@ -6,6 +6,12 @@
       parameters:
         name: getUser
       steps:
+        - to:
+            id: to-a36b
+            uri: amqp
+            parameters:
+              destinationName: hello
+              clientId: world
         - to:
             id: to-6d55
             uri: bean
@@ -26,7 +32,4 @@
                     constant:
                       id: constant-9d21
                       expression: 404
-#- beans:
-#    - name: userService
-#      type: org.apache.camel.example.rest.UserService
 - routeConfiguration: {}
diff --git a/karavan-designer/src/App.tsx b/karavan-designer/src/App.tsx
index 3a5b5fdd..466b9924 100644
--- a/karavan-designer/src/App.tsx
+++ b/karavan-designer/src/App.tsx
@@ -72,8 +72,8 @@ export function App() {
             fetch("metadata/spiBeans.json"),
             fetch("snippets/org.apache.camel.AggregationStrategy"),
             fetch("snippets/org.apache.camel.Processor"),
-            // fetch("example/demo.camel.yaml"),
-            fetch("example/avro-serialize-action.kamelet.yaml"),
+            fetch("example/demo.camel.yaml"),
+            // fetch("example/avro-serialize-action.kamelet.yaml"),
             // fetch("components/blocked-components.properties"),
             // fetch("kamelets/blocked-kamelets.properties")
             // fetch("example/plc4x-ads-source.kamelet.yaml")
diff --git a/karavan-designer/src/designer/route/element/DslElement.css 
b/karavan-designer/src/designer/route/element/DslElement.css
index 791acdb3..5ab8ecfd 100644
--- a/karavan-designer/src/designer/route/element/DslElement.css
+++ b/karavan-designer/src/designer/route/element/DslElement.css
@@ -235,6 +235,20 @@
     z-index: 100;
 }
 
+.karavan .step-element .copy-element-button {
+    position: absolute;
+    bottom: 9px;
+    line-height: 1;
+    border: 0;
+    padding: 0;
+    margin: 0 0 0 -30px;
+    background: transparent;
+    color: var(--pf-v5-global--primary-color--100);
+    visibility: hidden;
+    z-index: 100;
+}
+
+.karavan .step-element .header:hover .copy-element-button,
 .karavan .step-element .header:hover .insert-element-button {
     visibility: visible;
 }
diff --git a/karavan-designer/src/designer/route/element/DslElementHeader.tsx 
b/karavan-designer/src/designer/route/element/DslElementHeader.tsx
index db181cff..90eb5985 100644
--- a/karavan-designer/src/designer/route/element/DslElementHeader.tsx
+++ b/karavan-designer/src/designer/route/element/DslElementHeader.tsx
@@ -26,7 +26,7 @@ import {CamelDisplayUtil} from 
"karavan-core/lib/api/CamelDisplayUtil";
 import {useDesignerStore, useIntegrationStore} from "../../DesignerStore";
 import {shallow} from "zustand/shallow";
 import {useRouteDesignerHook} from "../useRouteDesignerHook";
-import {AddElementIcon, DeleteElementIcon, InsertElementIcon} from 
"../../utils/ElementIcons";
+import {AddElementIcon, DeleteElementIcon, InsertElementIcon, CopyElementIcon} 
from "../../utils/ElementIcons";
 import { RouteConfigurationDefinition} from 
"karavan-core/lib/model/CamelDefinition";
 import {AutoStartupIcon, ErrorHandlerIcon} from "../../icons/OtherIcons";
 
@@ -50,7 +50,8 @@ export function DslElementHeader(props: Props) {
         openSelector,
         isKamelet,
         isSourceKamelet,
-        isActionKamelet
+        isActionKamelet,
+        copyPasteStep
     } = useRouteDesignerHook();
 
     const [integration] = useIntegrationStore((s) => [s.integration], shallow)
@@ -217,6 +218,7 @@ export function DslElementHeader(props: Props) {
             !['FromDefinition', 'RouteConfigurationDefinition', 
'RouteTemplateDefinition', 'RouteDefinition', 'CatchDefinition', 
'FinallyDefinition', 'WhenDefinition', 
'OtherwiseDefinition'].includes(step.dslName)
             && !inRouteConfiguration;
         const showDeleteButton = !('RouteDefinition' === step.dslName && 
'RouteTemplateDefinition' === parent?.dslName);
+        const showCopyButton = ['ToDefinition', 'ToDynamicDefinition', 
'PollDefinition'].includes(step.dslName);
         const headerClasses = getHeaderClasses();
         const childrenInfo = getChildrenInfo(step) || [];
         const hasWideChildrenElement = getHasWideChildrenElement(childrenInfo)
@@ -250,6 +252,7 @@ export function DslElementHeader(props: Props) {
                 {!isDebugging && showInsertButton && getInsertElementButton()}
                 {!isDebugging && showDeleteButton && getDeleteButton()}
                 {!isDebugging && showAddButton && getAddElementButton()}
+                {!isDebugging && showCopyButton && getCopyElementButton()}
             </div>
         )
     }
@@ -313,8 +316,7 @@ export function DslElementHeader(props: Props) {
 
     function getAddElementButton() {
         return (
-            <Tooltip position={"bottom"}
-                     content={<div>{"Add DSL element to " + 
CamelDisplayUtil.getTitle(step)}</div>}>
+            <Tooltip position={"bottom"} content={<div>{"Add DSL element to " 
+ CamelDisplayUtil.getTitle(step)}</div>}>
                 <button
                     type="button"
                     aria-label="Add"
@@ -325,6 +327,24 @@ export function DslElementHeader(props: Props) {
             </Tooltip>
         )
     }
+    function getCopyElementButton() {
+        return (
+            <Tooltip position={"left"} content={"Copy element"}>
+                <button
+                    type="button"
+                    aria-label="Copy"
+                    onClick={e => {
+                        e.stopPropagation();
+                        if (props.parent) {
+                            copyPasteStep(step, props.parent?.uuid, 
props.position)
+                        }
+                    }}
+                    className={"copy-element-button"}>
+                    <CopyElementIcon/>
+                </button>
+            </Tooltip>
+        )
+    }
 
     function getInsertElementButton() {
         return (
diff --git a/karavan-designer/src/designer/route/useRouteDesignerHook.tsx 
b/karavan-designer/src/designer/route/useRouteDesignerHook.tsx
index 62cda4bb..ac92bfe5 100644
--- a/karavan-designer/src/designer/route/useRouteDesignerHook.tsx
+++ b/karavan-designer/src/designer/route/useRouteDesignerHook.tsx
@@ -182,6 +182,14 @@ export function useRouteDesignerHook() {
         }
     }
 
+    function copyPasteStep(step: CamelElement, parentUuid: string, position: 
number): void {
+        if (step) {
+            const clone = CamelUtil.cloneStep(step, true);
+            (clone as any).id = (clone as any).stepName + "-" + 
clone.uuid.substring(0,4);
+            addStep(clone, parentUuid, position + 1);
+        }
+    }
+
     function copyToClipboard(): void {
         const steps: CamelElement[] = []
         selectedUuids.forEach(selectedUuid => {
@@ -397,6 +405,6 @@ export function useRouteDesignerHook() {
     return {
         deleteElement, selectElement, moveElement, onShowDeleteConfirmation, 
onDslSelect, openSelector,
         createRouteConfiguration, onCommand, handleKeyDown, handleKeyUp, 
unselectElement, isKamelet, isSourceKamelet,
-        isActionKamelet, isSinkKamelet, openSelectorToReplaceFrom, 
createRouteTemplate
+        isActionKamelet, isSinkKamelet, openSelectorToReplaceFrom, 
createRouteTemplate, copyPasteStep
     }
 }
\ No newline at end of file
diff --git a/karavan-designer/src/designer/utils/ElementIcon.css 
b/karavan-designer/src/designer/utils/ElementIcon.css
index f89c962b..ee7d87dc 100644
--- a/karavan-designer/src/designer/utils/ElementIcon.css
+++ b/karavan-designer/src/designer/utils/ElementIcon.css
@@ -24,6 +24,13 @@
     vertical-align: text-bottom;
 }
 
+.copy-button-icon {
+    width: 20px;
+    height: 20px;
+    background: transparent;
+    vertical-align: text-bottom;
+}
+
 .delete-button-icon {
     fill: var(--pf-v5-global--danger-color--100);
     width: 20px;
diff --git a/karavan-designer/src/designer/utils/ElementIcons.tsx 
b/karavan-designer/src/designer/utils/ElementIcons.tsx
index 2d44bc5e..63c3ee43 100644
--- a/karavan-designer/src/designer/utils/ElementIcons.tsx
+++ b/karavan-designer/src/designer/utils/ElementIcons.tsx
@@ -18,6 +18,27 @@
 import "./ElementIcon.css"
 import React from 'react'
 
+export function CopyElementIcon() {
+    return (
+        <svg
+            xmlns="http://www.w3.org/2000/svg";
+            className="copy-button-icon"
+            fill="none"
+            viewBox="0 0 27 27"
+        >
+            <path
+                strokeLinecap="round"
+                strokeLinejoin="round"
+                strokeWidth={2}
+                style={{
+                    stroke: "var(--pf-v5-global--primary-color--100)",
+                }}
+                d="M9.41 2.34a9.996 9.996 0 0 1 9.661 2.589c3.905 3.905 3.905 
10.237 0 14.142-3.905 3.905-10.237 3.905-14.142 0a9.996 9.996 0 0 
1-2.59-9.66M15 9v6m0 0H9m6 0L5 5"
+            />
+        </svg>
+    )
+}
+
 export function DeleteElementIcon() {
     return (
         <svg
diff --git a/karavan-space/src/designer/route/element/DslElement.css 
b/karavan-space/src/designer/route/element/DslElement.css
index 791acdb3..5ab8ecfd 100644
--- a/karavan-space/src/designer/route/element/DslElement.css
+++ b/karavan-space/src/designer/route/element/DslElement.css
@@ -235,6 +235,20 @@
     z-index: 100;
 }
 
+.karavan .step-element .copy-element-button {
+    position: absolute;
+    bottom: 9px;
+    line-height: 1;
+    border: 0;
+    padding: 0;
+    margin: 0 0 0 -30px;
+    background: transparent;
+    color: var(--pf-v5-global--primary-color--100);
+    visibility: hidden;
+    z-index: 100;
+}
+
+.karavan .step-element .header:hover .copy-element-button,
 .karavan .step-element .header:hover .insert-element-button {
     visibility: visible;
 }
diff --git a/karavan-space/src/designer/route/element/DslElementHeader.tsx 
b/karavan-space/src/designer/route/element/DslElementHeader.tsx
index db181cff..90eb5985 100644
--- a/karavan-space/src/designer/route/element/DslElementHeader.tsx
+++ b/karavan-space/src/designer/route/element/DslElementHeader.tsx
@@ -26,7 +26,7 @@ import {CamelDisplayUtil} from 
"karavan-core/lib/api/CamelDisplayUtil";
 import {useDesignerStore, useIntegrationStore} from "../../DesignerStore";
 import {shallow} from "zustand/shallow";
 import {useRouteDesignerHook} from "../useRouteDesignerHook";
-import {AddElementIcon, DeleteElementIcon, InsertElementIcon} from 
"../../utils/ElementIcons";
+import {AddElementIcon, DeleteElementIcon, InsertElementIcon, CopyElementIcon} 
from "../../utils/ElementIcons";
 import { RouteConfigurationDefinition} from 
"karavan-core/lib/model/CamelDefinition";
 import {AutoStartupIcon, ErrorHandlerIcon} from "../../icons/OtherIcons";
 
@@ -50,7 +50,8 @@ export function DslElementHeader(props: Props) {
         openSelector,
         isKamelet,
         isSourceKamelet,
-        isActionKamelet
+        isActionKamelet,
+        copyPasteStep
     } = useRouteDesignerHook();
 
     const [integration] = useIntegrationStore((s) => [s.integration], shallow)
@@ -217,6 +218,7 @@ export function DslElementHeader(props: Props) {
             !['FromDefinition', 'RouteConfigurationDefinition', 
'RouteTemplateDefinition', 'RouteDefinition', 'CatchDefinition', 
'FinallyDefinition', 'WhenDefinition', 
'OtherwiseDefinition'].includes(step.dslName)
             && !inRouteConfiguration;
         const showDeleteButton = !('RouteDefinition' === step.dslName && 
'RouteTemplateDefinition' === parent?.dslName);
+        const showCopyButton = ['ToDefinition', 'ToDynamicDefinition', 
'PollDefinition'].includes(step.dslName);
         const headerClasses = getHeaderClasses();
         const childrenInfo = getChildrenInfo(step) || [];
         const hasWideChildrenElement = getHasWideChildrenElement(childrenInfo)
@@ -250,6 +252,7 @@ export function DslElementHeader(props: Props) {
                 {!isDebugging && showInsertButton && getInsertElementButton()}
                 {!isDebugging && showDeleteButton && getDeleteButton()}
                 {!isDebugging && showAddButton && getAddElementButton()}
+                {!isDebugging && showCopyButton && getCopyElementButton()}
             </div>
         )
     }
@@ -313,8 +316,7 @@ export function DslElementHeader(props: Props) {
 
     function getAddElementButton() {
         return (
-            <Tooltip position={"bottom"}
-                     content={<div>{"Add DSL element to " + 
CamelDisplayUtil.getTitle(step)}</div>}>
+            <Tooltip position={"bottom"} content={<div>{"Add DSL element to " 
+ CamelDisplayUtil.getTitle(step)}</div>}>
                 <button
                     type="button"
                     aria-label="Add"
@@ -325,6 +327,24 @@ export function DslElementHeader(props: Props) {
             </Tooltip>
         )
     }
+    function getCopyElementButton() {
+        return (
+            <Tooltip position={"left"} content={"Copy element"}>
+                <button
+                    type="button"
+                    aria-label="Copy"
+                    onClick={e => {
+                        e.stopPropagation();
+                        if (props.parent) {
+                            copyPasteStep(step, props.parent?.uuid, 
props.position)
+                        }
+                    }}
+                    className={"copy-element-button"}>
+                    <CopyElementIcon/>
+                </button>
+            </Tooltip>
+        )
+    }
 
     function getInsertElementButton() {
         return (
diff --git a/karavan-space/src/designer/route/useRouteDesignerHook.tsx 
b/karavan-space/src/designer/route/useRouteDesignerHook.tsx
index 62cda4bb..ac92bfe5 100644
--- a/karavan-space/src/designer/route/useRouteDesignerHook.tsx
+++ b/karavan-space/src/designer/route/useRouteDesignerHook.tsx
@@ -182,6 +182,14 @@ export function useRouteDesignerHook() {
         }
     }
 
+    function copyPasteStep(step: CamelElement, parentUuid: string, position: 
number): void {
+        if (step) {
+            const clone = CamelUtil.cloneStep(step, true);
+            (clone as any).id = (clone as any).stepName + "-" + 
clone.uuid.substring(0,4);
+            addStep(clone, parentUuid, position + 1);
+        }
+    }
+
     function copyToClipboard(): void {
         const steps: CamelElement[] = []
         selectedUuids.forEach(selectedUuid => {
@@ -397,6 +405,6 @@ export function useRouteDesignerHook() {
     return {
         deleteElement, selectElement, moveElement, onShowDeleteConfirmation, 
onDslSelect, openSelector,
         createRouteConfiguration, onCommand, handleKeyDown, handleKeyUp, 
unselectElement, isKamelet, isSourceKamelet,
-        isActionKamelet, isSinkKamelet, openSelectorToReplaceFrom, 
createRouteTemplate
+        isActionKamelet, isSinkKamelet, openSelectorToReplaceFrom, 
createRouteTemplate, copyPasteStep
     }
 }
\ No newline at end of file
diff --git a/karavan-space/src/designer/utils/ElementIcon.css 
b/karavan-space/src/designer/utils/ElementIcon.css
index f89c962b..ee7d87dc 100644
--- a/karavan-space/src/designer/utils/ElementIcon.css
+++ b/karavan-space/src/designer/utils/ElementIcon.css
@@ -24,6 +24,13 @@
     vertical-align: text-bottom;
 }
 
+.copy-button-icon {
+    width: 20px;
+    height: 20px;
+    background: transparent;
+    vertical-align: text-bottom;
+}
+
 .delete-button-icon {
     fill: var(--pf-v5-global--danger-color--100);
     width: 20px;
diff --git a/karavan-space/src/designer/utils/ElementIcons.tsx 
b/karavan-space/src/designer/utils/ElementIcons.tsx
index 2d44bc5e..63c3ee43 100644
--- a/karavan-space/src/designer/utils/ElementIcons.tsx
+++ b/karavan-space/src/designer/utils/ElementIcons.tsx
@@ -18,6 +18,27 @@
 import "./ElementIcon.css"
 import React from 'react'
 
+export function CopyElementIcon() {
+    return (
+        <svg
+            xmlns="http://www.w3.org/2000/svg";
+            className="copy-button-icon"
+            fill="none"
+            viewBox="0 0 27 27"
+        >
+            <path
+                strokeLinecap="round"
+                strokeLinejoin="round"
+                strokeWidth={2}
+                style={{
+                    stroke: "var(--pf-v5-global--primary-color--100)",
+                }}
+                d="M9.41 2.34a9.996 9.996 0 0 1 9.661 2.589c3.905 3.905 3.905 
10.237 0 14.142-3.905 3.905-10.237 3.905-14.142 0a9.996 9.996 0 0 
1-2.59-9.66M15 9v6m0 0H9m6 0L5 5"
+            />
+        </svg>
+    )
+}
+
 export function DeleteElementIcon() {
     return (
         <svg

Reply via email to