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