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='<Transparent Rectangle>' 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);