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 9f214e5  UI improvements (#193)
9f214e5 is described below

commit 9f214e5f9a0151a09fc170c69924449efa80b8de
Author: Marat Gubaidullin <marat.gubaidul...@gmail.com>
AuthorDate: Tue Feb 15 10:57:22 2022 -0500

    UI improvements (#193)
    
    * Icons and Titles
    
    * Direct and Seda connections
    
    * Direct and Seda connections
    
    * Direct and Seda connections
    
    * Saga connections
---
 karavan-core/src/core/api/ComponentApi.ts          |   7 +
 karavan-designer/src/App.tsx                       |  96 +++++++--
 karavan-designer/src/designer/karavan.css          |  17 ++
 .../src/designer/route/DslConnections.tsx          | 235 +++++++++++++++++----
 karavan-designer/src/designer/route/DslElement.tsx |   2 +-
 .../src/designer/route/DslSelector.tsx             |   6 +-
 karavan-designer/src/designer/utils/CamelUi.ts     |  80 ++++++-
 7 files changed, 373 insertions(+), 70 deletions(-)

diff --git a/karavan-core/src/core/api/ComponentApi.ts 
b/karavan-core/src/core/api/ComponentApi.ts
index f598d5a..7afedd1 100644
--- a/karavan-core/src/core/api/ComponentApi.ts
+++ b/karavan-core/src/core/api/ComponentApi.ts
@@ -15,6 +15,7 @@
  * limitations under the License.
  */
 import {Component, ComponentProperty} from "../model/ComponentModels";
+import {CamelMetadataApi} from "../model/CamelMetadata";
 
 export const Components: Component[] = [];
 
@@ -53,6 +54,12 @@ export const ComponentApi = {
         return uri.split(":")[0];
     },
 
+    getComponentTitleFromUri: (uri: string): string | undefined => {
+        const componentName =  uri.split(":")[0];
+        const title = ComponentApi.findByName(componentName)?.component.title;
+        return title ? title : componentName;
+    },
+
     getUriParts: (uri: string): Map<string, string> => {
         const result: Map<string, string> = new Map<string, string>();
         const name = ComponentApi.getComponentNameFromUri(uri);
diff --git a/karavan-designer/src/App.tsx b/karavan-designer/src/App.tsx
index 720650f..519e08e 100644
--- a/karavan-designer/src/App.tsx
+++ b/karavan-designer/src/App.tsx
@@ -35,6 +35,7 @@ class App extends React.Component<Props, State> {
 
     public state: State = {
         name: 'demo.yaml',
+        key: '',
         yaml: 'apiVersion: camel.apache.org/v1\n' +
             'kind: Integration\n' +
             'metadata:\n' +
@@ -48,6 +49,59 @@ class App extends React.Component<Props, State> {
             '        from:\n' +
             '          uri: kamelet:http-secured-source\n' +
             '          steps:\n' +
+            '            - saga:\n' +
+            '                steps:\n' +
+            '                  - kamelet:\n' +
+            '                      name: http-sink\n' +
+            '                  - kamelet:\n' +
+            '                      name: kafka-sink\n' +
+            '        id: Main Route\n' +
+            '    - route:\n' +
+            '        from:\n' +
+            '          uri: direct:completion\n' +
+            '        id: Completion\n' +
+            '    - route:\n' +
+            '        from:\n' +
+            '          uri: direct:compensation\n' +
+            '        id: Compensation\n' +
+            // '            - choice:\n' +
+            // '                when:\n' +
+            // '                  - expression:\n' +
+            // '                      simple:\n' +
+            // '                        expression: hello world\n' +
+            // '                    steps:\n' +
+            // '                      - to:\n' +
+            // '                          uri: direct:demo1\n' +
+            // '                  - expression:\n' +
+            // '                      simple:\n' +
+            // '                        expression: hello world\n' +
+            // '                    steps:\n' +
+            // '                      - to:\n' +
+            // '                          uri: direct:demo1\n' +
+            // '                  - expression:\n' +
+            // '                      simple:\n' +
+            // '                        expression: hello world\n' +
+            // '                    steps:\n' +
+            // '                      - to:\n' +
+            // '                          uri: direct:demo1\n' +
+            // '                  - expression:\n' +
+            // '                      simple:\n' +
+            // '                        expression: hello world\n' +
+            // '                    steps:\n' +
+            // '                      - wireTap:\n' +
+            // '                otherwise:\n' +
+            // '                  steps:\n' +
+            // '                    - to:\n' +
+            // '                        uri: direct:demo1\n' +
+            // '                    - to:\n' +
+            // '                        uri: direct\n' +
+            // '                    - kamelet:\n' +
+            // '                        name: insert-header-action\n' +
+            // '                    - kamelet:\n' +
+            // '                        name: http-sink\n' +
+            // '    - route:\n' +
+            // '        from:\n' +
+            // '          uri: direct:demo2\n' +
             // '           - saga: \n' +
             // '               option:\n' +
             // '                 - option-name: o1\n' +
@@ -76,26 +130,25 @@ class App extends React.Component<Props, State> {
             // '                      - log: "log1"\n' +
             // '                      - kamelet: \n' +
             // '                           name: http-sink \n' +
-            '            - choice:\n' +
-            '                when:\n' +
-            '                  - simple: "hello world"\n' +
-            '                    steps:\n' +
-            '                      - log:\n' +
-            '                           message: hello22s\n' +
-            '                           logName: log22\n' +
-            '                otherwise: {}\n'+
-            '    - beans:\n' +
-            '      - name: datasource\n' +
-            '        type: org.apache.commons.dbcp2.BasicDataSource\n' +
-            '        properties:\n' +
-            '          driverClassName: org.postgresql.Driver\n' +
-            '          password: postgres\n' +
-            '          url: "jdbc:postgresql:localhost:5432:demo"\n' +
-            '          username: postgres\n'+
-            '      - name: myAggregatorStrategy \n' +
-            '        type: 
org.apache.camel.processor.aggregate.UseLatestAggregationStrategy\n' +
-            '',
-        key: ''
+            // '            - choice:\n' +
+            // '                when:\n' +
+            // '                  - simple: "hello world"\n' +
+            // '                    steps:\n' +
+            // '                      - log:\n' +
+            // '                           message: hello22s\n' +
+            // '                           logName: log22\n' +
+            // '                otherwise: {}\n'+
+            // '    - beans:\n' +
+            // '      - name: datasource\n' +
+            // '        type: org.apache.commons.dbcp2.BasicDataSource\n' +
+            // '        properties:\n' +
+            // '          driverClassName: org.postgresql.Driver\n' +
+            // '          password: postgres\n' +
+            // '          url: "jdbc:postgresql:localhost:5432:demo"\n' +
+            // '          username: postgres\n'+
+            // '      - name: myAggregatorStrategy \n' +
+            // '        type: 
org.apache.camel.processor.aggregate.UseLatestAggregationStrategy\n' +
+            ''
     };
 
     componentDidMount() {
@@ -117,6 +170,7 @@ class App extends React.Component<Props, State> {
         ["bonita.json",
             "activemq.json",
             "direct.json",
+            "seda.json",
             "docker.json",
             "netty-http.json",
             "jms.json",
@@ -138,7 +192,7 @@ class App extends React.Component<Props, State> {
 
     save(filename: string, yaml: string) {
         // console.log(filename);
-        console.log(yaml);
+        // console.log(yaml);
     }
 
     public render() {
diff --git a/karavan-designer/src/designer/karavan.css 
b/karavan-designer/src/designer/karavan.css
index 4438b4f..7f6e749 100644
--- a/karavan-designer/src/designer/karavan.css
+++ b/karavan-designer/src/designer/karavan.css
@@ -478,6 +478,23 @@
     strokeWidth: 1;
     fill: transparent;
 }
+
+.karavan .dsl-page .graph .connections .path-direct {
+    stroke-dasharray: 0;
+    stroke: #fb8824;
+    strokeWidth: 1;
+    fill: transparent;
+}
+
+.karavan .dsl-page .graph .connections .path-seda {
+    stroke-dasharray: 2;
+    stroke: #fb8824;
+    -webkit-animation: dashdraw .5s linear infinite;
+    animation: dashdraw .5s linear infinite;
+    strokeWidth: 1;
+    fill: transparent;
+}
+
 @keyframes dashdraw{0%{stroke-dashoffset:10}}
 
 .karavan .dsl-page .flows .step-element {
diff --git a/karavan-designer/src/designer/route/DslConnections.tsx 
b/karavan-designer/src/designer/route/DslConnections.tsx
index 97efa57..8f37b34 100644
--- a/karavan-designer/src/designer/route/DslConnections.tsx
+++ b/karavan-designer/src/designer/route/DslConnections.tsx
@@ -19,8 +19,8 @@ import '../karavan.css';
 import {Integration, CamelElement} from 
"karavan-core/lib/model/IntegrationDefinition";
 import {DslPosition, EventBus} from "../utils/EventBus";
 import {CamelUi} from "../utils/CamelUi";
-import {ComponentApi} from "karavan-core/lib/api/ComponentApi";
 import {Subscription} from "rxjs";
+import {SagaDefinition} from 
"../../../../karavan-core/lib/model/CamelDefinition";
 
 interface Props {
     integration: Integration
@@ -35,7 +35,9 @@ interface State {
     steps: Map<string, DslPosition>
 }
 
-const overlapGap: number = 32;
+const overlapGap: number = 40;
+const outgoingDefinitions: string[] = ['ToDefinition', 'KameletDefinition', 
'ToDynamicDefinition', "PollEnrichDefinition", "EnrichDefinition", 
"WireTapDefinition", "SagaDefinition"];
+
 
 export class DslConnections extends React.Component<Props, State> {
 
@@ -45,7 +47,7 @@ export class DslConnections extends React.Component<Props, 
State> {
     };
 
     componentDidMount() {
-        const sub = EventBus.onPosition()?.subscribe((evt: DslPosition)=> 
this.setPosition(evt));
+        const sub = EventBus.onPosition()?.subscribe((evt: DslPosition) => 
this.setPosition(evt));
         this.setState({sub: sub});
     }
 
@@ -65,13 +67,14 @@ export class DslConnections extends React.Component<Props, 
State> {
     getIncomings() {
         let outs: [string, number][] = Array.from(this.state.steps.values())
             .filter(pos => ["FromDefinition"].includes(pos.step.dslName))
-            .sort((pos1: DslPosition, pos2: DslPosition ) => {
+            .filter(pos => !(pos.step.dslName === 'FromDefinition' && 
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]);
-        while (this.hasOverlap(outs)){
+        while (this.hasOverlap(outs)) {
             outs = this.addGap(outs);
         }
         return outs;
@@ -95,7 +98,8 @@ export class DslConnections extends React.Component<Props, 
State> {
             return (
                 <g key={pos.step.uuid + "-incoming"}>
                     <circle cx={incomingX} cy={fromY} r={r} 
className="circle-incoming"/>
-                    <image x={imageX} y={imageY} 
href={CamelUi.getIcon(pos.step)} className="icon"/>
+                    <image x={imageX} y={imageY} 
href={CamelUi.getConnectionIcon(pos.step)} className="icon"/>
+                    <text x={imageX - 5} y={imageY + 40} 
textAnchor="start">{CamelUi.getTitle(pos.step)}</text>
                     <path d={`M ${lineX1},${lineY1} C ${lineX1},${lineY2} 
${lineX2},${lineY1}  ${lineX2},${lineY2}`}
                           className="path-incoming" 
markerEnd="url(#arrowhead)"/>
                 </g>
@@ -106,7 +110,7 @@ export class DslConnections extends React.Component<Props, 
State> {
     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;
+            if (i > 0 && d[1] - arr[i - 1][1] < overlapGap) result = true;
         })
         return result;
     }
@@ -114,23 +118,25 @@ export class DslConnections extends 
React.Component<Props, State> {
     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])
+            if (i > 0 && d[1] - arr[i - 1][1] < overlapGap) result.push([d[0], 
d[1] + overlapGap])
             else result.push(d);
         })
         return result;
     }
 
-    getOutgoings():[string, number][] {
+
+    getOutgoings(): [string, number][] {
         let outs: [string, number][] = Array.from(this.state.steps.values())
-            .filter(pos => ['ToDefinition', 'KameletDefinition', 
'ToDynamicDefinition', "PollEnrichDefinition", "EnrichDefinition", 
"WireTapDefinition"].includes(pos.step.dslName))
+            .filter(pos => outgoingDefinitions.includes(pos.step.dslName))
             .filter(pos => pos.step.dslName !== 'KameletDefinition' || 
(pos.step.dslName === 'KameletDefinition' && 
!CamelUi.isActionKamelet(pos.step)))
-            .sort((pos1: DslPosition, pos2: DslPosition ) => {
+            .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]);
-        while (this.hasOverlap(outs)){
+        while (this.hasOverlap(outs)) {
             outs = this.addGap(outs);
         }
         return outs;
@@ -138,7 +144,7 @@ export class DslConnections extends React.Component<Props, 
State> {
 
     getOutgoing(data: [string, number]) {
         const pos = this.state.steps.get(data[0]);
-        if (pos){
+        if (pos) {
             const fromX = pos.headerRect.x + pos.headerRect.width / 2;
             const fromY = pos.headerRect.y + pos.headerRect.height / 2 - 
this.props.top;
             const r = pos.headerRect.height / 2;
@@ -151,24 +157,17 @@ export class DslConnections extends 
React.Component<Props, State> {
             const lineX2 = outgoingX - r * 2 + 4;
             const lineY2 = outgoingY;
 
-            const imageX = outgoingX - r + 5;
-            const imageY = outgoingY - r + 5;
-
-            let image = CamelUi.getIcon(pos.step);
-            if ((pos.step as any).uri){
-                const labels =  ComponentApi.findByName((pos.step as 
any).uri)?.component.label;
-                if (labels){
-                    // labels
-                }
-            }
-
             const lineXi = lineX1 + 40;
             const lineYi = lineY2;
 
+            let image = CamelUi.getConnectionIcon(pos.step);
+            const imageX = outgoingX - r + 5;
+            const imageY = outgoingY - r + 5;
             return (
                 <g key={pos.step.uuid + "-outgoing"}>
                     <circle cx={outgoingX} cy={outgoingY} r={r} 
className="circle-outgoing"/>
                     <image x={imageX} y={imageY} href={image} 
className="icon"/>
+                    <text x={imageX + 25} y={imageY + 40} 
textAnchor="end">{CamelUi.getOutgoingTitle(pos.step)}</text>
                     <path d={`M ${lineX1},${lineY1} C ${lineXi - 20}, 
${lineY1} ${lineX1 - 15},${lineYi} ${lineXi},${lineYi} L ${lineX2},${lineY2}`}
                           className="path-incoming" 
markerEnd="url(#arrowhead)"/>
                 </g>
@@ -176,6 +175,165 @@ export class DslConnections extends 
React.Component<Props, State> {
         }
     }
 
+    getIntegrals(): [string, number][] {
+        let outs: [string, number][] = Array.from(this.state.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]);
+        return outs;
+    }
+
+    getInternalLines(data: [string, number]) {
+        const pos = this.state.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;
+            const fromY = pos.headerRect.y + pos.headerRect.height / 2 - 
this.props.top;
+            const r = pos.headerRect.height / 2;
+            const className = CamelUi.hasDirectUri(pos.step) ? "path-direct" : 
"path-seda";
+            return this.getInternalLine(uri, key, className, fromX, fromY, r);
+        } else if (pos?.step.dslName === 'SagaDefinition'){
+            const saga = (pos?.step as SagaDefinition);
+            const fromX = pos.headerRect.x + pos.headerRect.width / 2;
+            const fromY = pos.headerRect.y + pos.headerRect.height / 2 - 
this.props.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));
+            }
+            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));
+            }
+            return result;
+        }
+    }
+
+    getInternalLine(uri: string, key: string, className: string, fromX: 
number, fromY: number, r: number) {
+        const target = Array.from(this.state.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;
+            const targetY = target.headerRect.y + target.headerRect.height / 2 
- this.props.top;
+            const gap = 100;
+
+            // right
+            if (targetX - fromX >= gap) {
+                const startX = fromX + r;
+                const startY = fromY;
+                const endX = targetX - r * 2 + 4;
+                const endY = targetY;
+
+                const coefX = 24;
+                const coefY = (targetY > fromY) ? 24 : -24;
+
+                const pointX1 = startX + coefX;
+                const pointY1 = startY;
+                const pointX2 = startX + coefX;
+                const pointY2 = startY + coefY;
+
+                const pointLX = pointX1;
+                const pointLY = targetY - coefY;
+
+                const pointX3 = pointLX;
+                const pointY3 = endY;
+                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);
+            } else if (targetX > fromX && targetX - fromX < gap) {
+                const startX = fromX - r;
+                const startY = fromY;
+                const endX = targetX - r * 2 + 4;
+                const endY = targetY;
+
+                const coefX = -24;
+                const coefY = (targetY > fromY) ? 24 : -24;
+
+                const pointX1 = startX + coefX;
+                const pointY1 = startY;
+                const pointX2 = startX + coefX;
+                const pointY2 = startY + coefY;
+
+                const pointLX = pointX1;
+                const pointLY = targetY - coefY;
+
+                const pointX3 = pointLX;
+                const pointY3 = endY;
+                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);
+            } else if (targetX <= fromX && fromX - targetX < gap) {
+                const startX = fromX + r;
+                const startY = fromY;
+                const endX = targetX + r * 2 - 4;
+                const endY = targetY;
+
+                const coefX = 24;
+                const coefY = (targetY > fromY) ? 24 : -24;
+
+                const pointX1 = startX + coefX;
+                const pointY1 = startY;
+                const pointX2 = startX + coefX;
+                const pointY2 = startY + coefY;
+
+                const pointLX = pointX1;
+                const pointLY = targetY - coefY;
+
+                const pointX3 = pointLX;
+                const pointY3 = endY;
+                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);
+            } else {
+                const startX = fromX - r;
+                const startY = fromY;
+                const endX = targetX + r * 2 - 4;
+                const endY = targetY;
+
+                const coefX = -24;
+                const coefY = (targetY > fromY) ? 24 : -24;
+
+                const pointX1 = startX + coefX;
+                const pointY1 = startY;
+                const pointX2 = startX + coefX;
+                const pointY2 = startY + coefY;
+
+                const pointLX = pointX1;
+                const pointLY = targetY - coefY;
+
+                const pointX3 = pointLX;
+                const pointY3 = endY;
+                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);
+            }
+        }
+    }
+
+    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}>
+                <path d={`M ${startX} ${startY} 
+                        Q ${pointX1} ${pointY1} ${pointX2} ${pointY2} L 
${pointLX},${pointLY}
+                        Q ${pointX3} ${pointY3} ${pointX4} ${pointY4} L 
${endX},${endY}`}
+                      className={className} markerEnd="url(#arrowhead)"/>
+            </g>
+        )
+    }
+
     getCircle(pos: DslPosition) {
         const cx = pos.headerRect.x + pos.headerRect.width / 2;
         const cy = pos.headerRect.y + pos.headerRect.height / 2 - 
this.props.top;
@@ -185,12 +343,12 @@ export class DslConnections extends 
React.Component<Props, State> {
         )
     }
 
-    hasSteps = (step: CamelElement):boolean => {
-        return (step.hasSteps() &&  !['FromDefinition'].includes(step.dslName))
-            ||  ['RouteDefinition', 'TryDefinition', 'ChoiceDefinition', 
'SwitchDefinition'].includes(step.dslName);
+    hasSteps = (step: CamelElement): boolean => {
+        return (step.hasSteps() && !['FromDefinition'].includes(step.dslName))
+            || ['RouteDefinition', 'TryDefinition', 'ChoiceDefinition', 
'SwitchDefinition'].includes(step.dslName);
     }
 
-    getPreviousStep(pos: DslPosition){
+    getPreviousStep(pos: DslPosition) {
         return Array.from(this.state.steps.values())
             .filter(p => pos.parent?.uuid === p.parent?.uuid)
             .filter(p => p.inSteps)
@@ -200,24 +358,24 @@ export class DslConnections extends 
React.Component<Props, State> {
     getArrow(pos: DslPosition) {
         const endX = pos.headerRect.x + pos.headerRect.width / 2;
         const endY = pos.headerRect.y - 9 - this.props.top;
-        if (pos.parent){
+        if (pos.parent) {
             const parent = this.state.steps.get(pos.parent.uuid);
-            if (parent){
+            if (parent) {
                 const startX = parent.headerRect.x + parent.headerRect.width / 
2;
                 const startY = parent.headerRect.y + parent.headerRect.height 
- this.props.top;
-                if (!pos.inSteps || (pos.inSteps && pos.position === 0) && 
parent.step.dslName !== 'MulticastDefinition'){
+                if (!pos.inSteps || (pos.inSteps && pos.position === 0) && 
parent.step.dslName !== 'MulticastDefinition') {
                     return (
                         <path d={`M ${startX},${startY} C ${startX},${endY} 
${endX},${startY}   ${endX},${endY}`}
                               className="path" key={pos.step.uuid} 
markerEnd="url(#arrowhead)"/>
                     )
-                } else if (parent.step.dslName === 'MulticastDefinition' && 
pos.inSteps){
+                } else if (parent.step.dslName === 'MulticastDefinition' && 
pos.inSteps) {
                     return (
                         <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)){
+                } else if (pos.inSteps && pos.position > 0 && 
!this.hasSteps(pos.step)) {
                     const prev = this.getPreviousStep(pos);
-                    if (prev){
+                    if (prev) {
                         const r = this.hasSteps(prev.step) ? prev.rect : 
prev.headerRect;
                         const prevX = r.x + r.width / 2;
                         const prevY = r.y + r.height - this.props.top;
@@ -225,9 +383,9 @@ export class DslConnections extends React.Component<Props, 
State> {
                             <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)){
+                } else if (pos.inSteps && pos.position > 0 && 
this.hasSteps(pos.step)) {
                     const prev = this.getPreviousStep(pos);
-                    if (prev){
+                    if (prev) {
                         const r = this.hasSteps(prev.step) ? prev.rect : 
prev.headerRect;
                         const prevX = r.x + r.width / 2;
                         const prevY = r.y + r.height - this.props.top;
@@ -244,17 +402,18 @@ export class DslConnections extends 
React.Component<Props, State> {
         const steps = Array.from(this.state.steps.values());
         return (
             <svg
-                style={{ width: this.props.width, height: this.props.height, 
position: "absolute", left: 0, top: 0}}
+                style={{width: this.props.width, height: this.props.height, 
position: "absolute", left: 0, top: 0}}
                 viewBox={"0 0 " + this.props.width + " " + this.props.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" />
+                        <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.getIntegrals().map(p => this.getInternalLines(p)).flat()}
             </svg>
         )
     }
diff --git a/karavan-designer/src/designer/route/DslElement.tsx 
b/karavan-designer/src/designer/route/DslElement.tsx
index c955f47..117599d 100644
--- a/karavan-designer/src/designer/route/DslElement.tsx
+++ b/karavan-designer/src/designer/route/DslElement.tsx
@@ -193,7 +193,7 @@ export class DslElement extends React.Component<Props, 
State> {
                 }
                 <div className={this.hasWideChildrenElement() ? "header-text" 
: ""}>
                     {this.hasWideChildrenElement() && <div 
className="spacer"/>}
-                    <Text className={this.hasWideChildrenElement() ? "text 
text-right" :"text text-bottom"}>{CamelUi.getTitle(this.state.step)}</Text>
+                    <Text className={this.hasWideChildrenElement() ? "text 
text-right" :"text 
text-bottom"}>{CamelUi.getElementTitle(this.state.step)}</Text>
                 </div>
                 {this.state.step.dslName !== 'FromDefinition' && <button 
type="button" aria-label="Delete" onClick={e => this.delete(e)} 
className="delete-button"><DeleteIcon noVerticalAlign/></button>}
                 {showAddButton && this.getAddElementButton()}
diff --git a/karavan-designer/src/designer/route/DslSelector.tsx 
b/karavan-designer/src/designer/route/DslSelector.tsx
index 04c9e3e..927e70b 100644
--- a/karavan-designer/src/designer/route/DslSelector.tsx
+++ b/karavan-designer/src/designer/route/DslSelector.tsx
@@ -22,7 +22,7 @@ import {
     Text, TextInput,
 } from '@patternfly/react-core';
 import '../karavan.css';
-import {CamelUi} from "../utils/CamelUi";
+import {camelIcon, CamelUi} from "../utils/CamelUi";
 import {DslMetaModel} from "../utils/DslMetaModel";
 import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
 
@@ -88,6 +88,8 @@ export class DslSelector extends React.Component<Props, 
State> {
         } else if ((dsl.dsl && dsl.dsl === "FromDefinition")
             && dsl.uri?.startsWith("kamelet")) {
             return CamelUi.getKameletIconByUri(dsl.uri);
+        } else if (dsl.navigation === 'component' ){
+            return camelIcon;
         } else {
             return CamelUi.getIconForName(dsl.dsl);
         }
@@ -114,7 +116,7 @@ export class DslSelector extends React.Component<Props, 
State> {
                         </div>}
                     {dsl.navigation.toLowerCase() === "component"
                         && <div className="footer" style={{justifyContent: 
"flex-start"}}>
-                            {dsl.labels.split(',').map((s: string) => <Badge 
isRead className="labels">{s}</Badge>)}
+                            {dsl.labels.split(',').map((s: string) => <Badge 
key={s} isRead className="labels">{s}</Badge>)}
                         </div>}
                 </CardFooter>
             </Card>
diff --git a/karavan-designer/src/designer/utils/CamelUi.ts 
b/karavan-designer/src/designer/utils/CamelUi.ts
index a023e72..3b63f6e 100644
--- a/karavan-designer/src/designer/utils/CamelUi.ts
+++ b/karavan-designer/src/designer/utils/CamelUi.ts
@@ -22,7 +22,7 @@ import {ComponentProperty} from 
"karavan-core/lib/model/ComponentModels";
 import {CamelMetadataApi} from "karavan-core/lib/model/CamelMetadata";
 import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
 import {CamelDefinitionApiExt} from 
"karavan-core/lib/api/CamelDefinitionApiExt";
-import { KameletDefinition, NamedBeanDefinition, RouteDefinition} from 
"karavan-core/lib/model/CamelDefinition";
+import {KameletDefinition, NamedBeanDefinition, RouteDefinition, 
SagaDefinition} from "karavan-core/lib/model/CamelDefinition";
 import {CamelElement, Dependency, Integration} from 
"karavan-core/lib/model/IntegrationDefinition";
 
 const StepElements: string[] = [
@@ -59,9 +59,12 @@ const StepElements: string[] = [
     "WireTapDefinition"
 ];
 
-const defaultIcon =
+export const camelIcon =
     "data:image/svg+xml,%3Csvg viewBox='0 0 130.21 130.01' 
xmlns='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3ClinearGradient id='a' 
x1='333.48' x2='477' y1='702.6' y2='563.73' gradientTransform='translate(94.038 
276.06) scale(.99206)' gradientUnits='userSpaceOnUse'%3E%3Cstop 
stop-color='%23F69923' offset='0'/%3E%3Cstop stop-color='%23F79A23' 
offset='.11'/%3E%3Cstop stop-color='%23E97826' 
offset='.945'/%3E%3C/linearGradient%3E%3ClinearGradient id='b' x1='333.48' 
x2='477' y1='702.6' y2='563 [...]
 
+export const externalIcon =
+    "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32px' 
height='32px' viewBox='0 0 32 32' 
id='icon'%3E%3Cdefs%3E%3Cstyle%3E.cls-1%7Bfill:none;%7D%3C/style%3E%3C/defs%3E%3Ctitle%3Efog%3C/title%3E%3Cpath
 
d='M25.8289,13.1155A10.02,10.02,0,0,0,16,5.0005V7a8.0233,8.0233,0,0,1,7.8649,6.4934l.2591,1.346,1.3488.2441A5.5019,5.5019,0,0,1,24.5076,26H16v2h8.5076a7.5019,7.5019,0,0,0,1.3213-14.8845Z'/%3E%3Crect
 x='8' y='24' width='6' height='2'/%3E%3Crect x='4' y='24' width='2'  [...]
+
 export class CamelUi {
 
     static getSelectorModelTypes = (parentDsl: string | undefined, showSteps: 
boolean = true): string[] => {
@@ -193,6 +196,30 @@ export class CamelUi {
         else return false;
     }
 
+    static hasInternalUri = (element: CamelElement): boolean => {
+        return this.hasDirectUri(element) || this.hasSedaUri(element);
+    }
+
+    static hasDirectUri = (element: CamelElement): boolean => {
+        return this.hasUriStartWith(element,'direct');
+    }
+
+    static hasSedaUri = (element: CamelElement): boolean => {
+        return this.hasUriStartWith(element,'seda');
+    }
+
+    static hasUriStartWith = (element: CamelElement, text: string): boolean => 
{
+        if ((element as any).uri && typeof (element as any).uri === 'string') {
+            return (element as any).uri.startsWith(text);
+        } else if (element.dslName === 'SagaDefinition'){
+            const completion = (element as SagaDefinition).completion || '';
+            const compensation = (element as SagaDefinition).compensation || 
'';
+            return completion.startsWith(text) || 
compensation.startsWith(text);
+        } else {
+            return false;
+        }
+    }
+
     static getKameletProperties = (element: any): Property[] => {
         const kamelet = CamelUi.getKamelet(element)
         return kamelet
@@ -217,6 +244,19 @@ export class CamelUi {
        }
     }
 
+    static getElementTitle = (element: CamelElement): string => {
+        if (element.dslName === 'RouteDefinition') {
+            const routeId = (element as RouteDefinition).id
+            return routeId ? routeId : CamelUtil.capitalizeName((element as 
any).stepName);
+        } else if (['ToDefinition', 'ToDynamicDefinition', 'FromDefinition', 
'KameletDefinition'].includes(element.dslName) && (element as any).uri) {
+            const uri = (element as any).uri
+            return 
CamelUtil.capitalizeName(ComponentApi.getComponentTitleFromUri(uri) || '');
+        } else {
+            const title = 
CamelMetadataApi.getCamelModelMetadataByClassName(element.dslName);
+            return title ? title.title : CamelUtil.capitalizeName((element as 
any).stepName);
+        }
+    }
+
     static getTitle = (element: CamelElement): string => {
         const k: KameletModel | undefined = CamelUi.getKamelet(element);
         if (k) {
@@ -226,13 +266,28 @@ export class CamelUi {
             return routeId ? routeId : CamelUtil.capitalizeName((element as 
any).stepName);
         } else if ((element as any).uri) {
             const uri = (element as any).uri
-            return ComponentApi.getComponentNameFromUri(uri) || '';
+            return ComponentApi.getComponentTitleFromUri(uri) || '';
         } else {
             const title = 
CamelMetadataApi.getCamelModelMetadataByClassName(element.dslName);
             return title ? title.title : CamelUtil.capitalizeName((element as 
any).stepName);
         }
     }
 
+    static getOutgoingTitle = (element: CamelElement): string => {
+        const k: KameletModel | undefined = CamelUi.getKamelet(element);
+        if (k) {
+            return k.title();
+        } else if (element.dslName === 'RouteDefinition') {
+            const routeId = (element as RouteDefinition).id
+            return routeId ? routeId : CamelUtil.capitalizeName((element as 
any).stepName);
+        } else if ((element as any).uri) {
+            const uri = (element as any).uri
+            return ComponentApi.getComponentTitleFromUri(uri) || uri;
+        } else {
+            return "";
+        }
+    }
+
     static isShowExpressionTooltip = (element: CamelElement): boolean => {
         if (element.hasOwnProperty("expression")){
             const exp = CamelDefinitionApiExt.getExpressionValue((element as 
any).expression);
@@ -308,7 +363,7 @@ export class CamelUi {
             case "WireTapDefinition":
                 return "data:image/svg+xml,%3Csvg width='16' height='16' 
xmlns='http://www.w3.org/2000/svg' 
xmlns:svg='http://www.w3.org/2000/svg'%3E%3Cg class='layer'%3E%3Ctitle%3ELayer 
1%3C/title%3E%3Cpath d='m8,8c1.86658,0 3.4346,1.27853 
3.8759,3.0076l0.06156,-0.00568l0,0l3.06254,-0.00192c0.5523,0 1,0.4477 
1,1c0,0.5523 -0.4477,1 -1,1l-3,0c-0.042,0 -0.0834,-0.0026 
-0.1241,-0.0076c-0.4413,1.7291 -2.00932,3.0076 -3.8759,3.0076c-1.86658,0 
-3.43455,-1.2785 -3.87594,-3.0076c-0.04064,0.005 - [...]
             case "ToDynamicDefinition":
-                return "data:image/svg+xml,%0A%3Csvg width='32px' 
height='32px' viewBox='0 0 32 32' id='icon' 
xmlns='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3Cstyle%3E .cls-1 %7B fill: 
none; %7D %3C/style%3E%3C/defs%3E%3Cpath 
d='M28.5039,8.1362l-12-7a1,1,0,0,0-1.0078,0l-12,7A1,1,0,0,0,3,9V23a1,1,0,0,0,.4961.8638l12,7a1,1,0,0,0,1.0078,0l12-7A1,1,0,0,0,29,23V9A1,1,0,0,0,28.5039,8.1362ZM16,3.1577,26.0156,9,16,14.8423,5.9844,9ZM5,10.7412l10,5.833V28.2588L5,22.4258ZM17,28.2588V16.5742l10-5.8
 [...]
+                return "data:image/svg+xml,%3Csvg width='32' height='32' 
xmlns='http://www.w3.org/2000/svg' 
xmlns:svg='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3Cstyle%3E.cls-1 %7B fill: 
none; %7D%3C/style%3E%3C/defs%3E%3Cg class='layer'%3E%3Ctitle%3ELayer 
1%3C/title%3E%3Cpath 
d='m24.08924,9.30736l-1.67194,1.57734l4.23986,3.99986l-5.47865,0a5.92883,5.59337
 0 0 0 -4.60981,-4.34899l0,-10.15173l-2.36467,0l0,10.15173a5.91168,5.5772 0 0 0 
0,10.92886l0,10.15173l2.36467,0l0,-10.15173a5.92883,5. [...]
             case "RemoveHeaderDefinition":
                 return "data:image/svg+xml,%3Csvg width='32' height='32' 
xmlns='http://www.w3.org/2000/svg' 
xmlns:svg='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3Cstyle%3E.cls-1 %7B fill: 
none; %7D%3C/style%3E%3C/defs%3E%3Cg class='layer'%3E%3Ctitle%3ELayer 
1%3C/title%3E%3Cpath d='m24,30l-20,0a2.0021,2.0021 0 0 1 
-2,-2l0,-6a2.0021,2.0021 0 0 1 2,-2l20,0a2.0021,2.0021 0 0 1 
2,2l0,6a2.0021,2.0021 0 0 1 -2,2zm-20,-8l-0.0015,0l0.0015,6l20,0l0,-6l-20,0z' 
id='svg_1'/%3E%3Cpolygon id='svg_2' poi [...]
             case "RemoveHeadersDefinition":
@@ -336,9 +391,9 @@ export class CamelUi {
             case "SagaDefinition":
                 return "data:image/svg+xml,%0A%3Csvg width='32px' 
height='32px' viewBox='0 0 32 32' id='icon' 
xmlns='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3Cstyle%3E.cls-1%7Bfill:none;%7D%3C/style%3E%3C/defs%3E%3Ctitle%3Eexpand-categories%3C/title%3E%3Crect
 x='20' y='26' width='6' height='2'/%3E%3Crect x='20' y='18' width='8' 
height='2'/%3E%3Crect x='20' y='10' width='10' height='2'/%3E%3Crect x='15' 
y='4' width='2' height='24'/%3E%3Cpolygon points='10.586 3.959 7 7.249 3.412 
3.958 2  [...]
             case "FromDefinition":
-                return "data:image/svg+xml,%0A%3Csvg width='24px' 
height='24px' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath 
fill-rule='evenodd' d='M15.8198039,12 L21.2037682,12 L12,22.5185923 
L2.79623177,12 L8.1801961,12 L10.1801961,2 L13.8198039,2 L15.8198039,12 Z 
M12,19.4814077 L16.7962318,14 L14.1801961,14 L12.1801961,4 L11.8198039,4 
L9.8198039,14 L7.20376823,14 L12,19.4814077 Z'/%3E%3C/svg%3E";
+                return "data:image/svg+xml,%3Csvg 
xmlns='http://www.w3.org/2000/svg' width='24px' height='24px' viewBox='0 0 24 
24'%3E%3Cpath fill-rule='evenodd' d='M12.6577283,22.7532553 L12,23.3275712 
L11.3422717,22.7532553 C5.81130786,17.9237218 3,13.70676 3,10 C3,4.7506636 
7.09705254,1 12,1 C16.9029475,1 21,4.7506636 21,10 C21,13.70676 
18.1886921,17.9237218 12.6577283,22.7532553 Z M5,10 C5,12.8492324 
7.30661202,16.4335466 12,20.6634039 C16.693388,16.4335466 19,12.8492324 19,10 
C19,5. [...]
             case "ToDefinition":
-                return "data:image/svg+xml,%0A%3Csvg width='32px' 
height='32px' viewBox='0 0 32 32' id='icon' 
xmlns='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3Cstyle%3E .cls-1 %7B fill: 
none; %7D %3C/style%3E%3C/defs%3E%3Cpath 
d='M28.5039,8.1362l-12-7a1,1,0,0,0-1.0078,0l-12,7A1,1,0,0,0,3,9V23a1,1,0,0,0,.4961.8638l12,7a1,1,0,0,0,1.0078,0l12-7A1,1,0,0,0,29,23V9A1,1,0,0,0,28.5039,8.1362ZM16,3.1577,26.0156,9,16,14.8423,5.9844,9ZM5,10.7412l10,5.833V28.2588L5,22.4258ZM17,28.2588V16.5742l10-5.8
 [...]
+                return "data:image/svg+xml,%3Csvg width='32' height='32' 
xmlns='http://www.w3.org/2000/svg' 
xmlns:svg='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3Cstyle%3E.cls-1 %7B fill: 
none; %7D%3C/style%3E%3C/defs%3E%3Cg class='layer'%3E%3Ctitle%3ELayer 
1%3C/title%3E%3Cpath 
d='m24.08924,9.30736l-1.67194,1.57734l4.23986,3.99986l-5.47865,0a5.92883,5.59337
 0 0 0 -4.60981,-4.34899l0,-10.15173l-2.36467,0l0,10.15173a5.91168,5.5772 0 0 0 
0,10.92886l0,10.15173l2.36467,0l0,-10.15173a5.92883,5. [...]
             case "SwitchDefinition":
                 return "data:image/svg+xml,%3Csvg width='32' height='32' 
xmlns='http://www.w3.org/2000/svg' 
xmlns:svg='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3Cstyle%3E.cls-1 %7B fill: 
none; %7D%3C/style%3E%3C/defs%3E%3Ctitle%3Emilestone%3C/title%3E%3Cg 
class='layer'%3E%3Ctitle%3ELayer 1%3C/title%3E%3Cpath 
d='m24.5857,6.5859a1.9865,1.9865 0 0 0 
-1.4143,-0.5859l-7.1714,0l0,-4l-2,0l0,4l-8,0a2.0025,2.0025 0 0 0 
-2,2l0,6a2.0025,2.0025 0 0 0 2,2l8,0l0,14l2,0l0,-14l7.1714,0a1.9865,1.9865 0 0 
[...]
             case "KameletDefinition":
@@ -346,19 +401,28 @@ export class CamelUi {
             case "DynamicRouterDefinition":
                 return "data:image/svg+xml,%3Csvg width='32' height='32' 
xmlns='http://www.w3.org/2000/svg' 
xmlns:svg='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3Cstyle%3E.cls-1%7Bfill:none;%7D%3C/style%3E%3C/defs%3E%3Ctitle%3Einsert%3C/title%3E%3Cg
 class='layer'%3E%3Ctitle%3ELayer 1%3C/title%3E%3Crect class='cls-1' 
data-name='&lt;Transparent Rectangle&gt;' height='32' 
id='_Transparent_Rectangle_' width='31.94228' x='0' y='0'/%3E%3Cg id='svg_5' 
transform='matrix(1 0 0 1 0 0) rotate(180 16 [...]
             default:
-                return defaultIcon;
+                return camelIcon;
         }
     }
 
     static getIcon = (element: CamelElement): string => {
         const k: KameletModel | undefined = CamelUi.getKamelet(element);
         if (["FromDefinition", "KameletDefinition"].includes(element.dslName)) 
{
-            return k ? k.icon() : defaultIcon;
+            return k ? k.icon() : CamelUi.getIconForName(element.dslName);
         } else {
             return CamelUi.getIconForName(element.dslName);
         }
     }
 
+    static getConnectionIcon = (element: CamelElement): string => {
+        const k: KameletModel | undefined = CamelUi.getKamelet(element);
+        if (["FromDefinition", "KameletDefinition"].includes(element.dslName)) 
{
+            return k ? k.icon() : externalIcon;
+        } else {
+            return externalIcon;
+        }
+    }
+
     static getFlowCounts = (i: Integration): Map<string, number> => {
         const result = new Map<string, number>();
         result.set('routes', i.spec.flows?.filter((e: any) => e.dslName === 
'RouteDefinition').length || 0);

Reply via email to