This is an automated email from the ASF dual-hosted git repository.
marat pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-karavan.git
The following commit(s) were added to refs/heads/main by this push:
new a983bd1b Fix #1449
a983bd1b is described below
commit a983bd1b8a2c0ff64ed370d649a291e20ad16c78
Author: Marat Gubaidullin <[email protected]>
AuthorDate: Mon Oct 28 16:32:46 2024 -0400
Fix #1449
---
.../src/main/webui/src/designer/icons/EipIcons.tsx | 4 +-
.../webui/src/designer/property/DslProperties.css | 10 +-
.../src/designer/property/PropertiesHeader.tsx | 259 ++++++++++++---------
.../src/designer/property/usePropertiesHook.tsx | 1 +
.../webui/src/designer/route/DslConnections.css | 9 +
.../webui/src/designer/route/DslConnections.tsx | 34 +--
.../src/designer/route/element/DslElement.css | 4 +
.../src/main/webui/src/designer/utils/CamelUi.tsx | 4 +-
karavan-core/src/core/api/CamelDisplayUtil.ts | 4 +-
karavan-core/src/core/api/CamelUtil.ts | 23 +-
karavan-designer/src/designer/icons/EipIcons.tsx | 4 +-
.../src/designer/property/DslProperties.css | 10 +-
.../src/designer/property/PropertiesHeader.tsx | 259 ++++++++++++---------
.../src/designer/property/usePropertiesHook.tsx | 1 +
.../src/designer/route/DslConnections.css | 9 +
.../src/designer/route/DslConnections.tsx | 34 +--
.../src/designer/route/element/DslElement.css | 4 +
karavan-designer/src/designer/utils/CamelUi.tsx | 4 +-
karavan-space/src/designer/icons/EipIcons.tsx | 4 +-
.../src/designer/property/DslProperties.css | 10 +-
.../src/designer/property/PropertiesHeader.tsx | 259 ++++++++++++---------
.../src/designer/property/usePropertiesHook.tsx | 1 +
.../src/designer/route/DslConnections.css | 9 +
.../src/designer/route/DslConnections.tsx | 34 +--
.../src/designer/route/element/DslElement.css | 4 +
karavan-space/src/designer/utils/CamelUi.tsx | 4 +-
26 files changed, 608 insertions(+), 394 deletions(-)
diff --git a/karavan-app/src/main/webui/src/designer/icons/EipIcons.tsx
b/karavan-app/src/main/webui/src/designer/icons/EipIcons.tsx
index fc928d9f..4bdac684 100644
--- a/karavan-app/src/main/webui/src/designer/icons/EipIcons.tsx
+++ b/karavan-app/src/main/webui/src/designer/icons/EipIcons.tsx
@@ -50,14 +50,14 @@ export function AggregateIcon() {
);
}
-export function ToIcon() {
+export function ToIcon(classname: string = '') {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={800}
height={800}
viewBox="0 0 32 32"
- className="icon"
+ className={classname ? "icon " + classname : "icon"}
>
<path d="m12.103 11.923 2.58 2.59H2.513v2h12.17l-2.58 2.59 1.41
1.41 5-5-5-5z"/>
<path
diff --git a/karavan-app/src/main/webui/src/designer/property/DslProperties.css
b/karavan-app/src/main/webui/src/designer/property/DslProperties.css
index 97b97a38..e2401cf3 100644
--- a/karavan-app/src/main/webui/src/designer/property/DslProperties.css
+++ b/karavan-app/src/main/webui/src/designer/property/DslProperties.css
@@ -24,14 +24,22 @@
width: 100%;
display: flex;
flex-direction: row;
+ /*justify-content: space-between;*/
+ gap: 10px;
}
.karavan .properties .headers .top h1 {
- width: 100%;
+ /*width: 100%;*/
margin-top: auto;
margin-bottom: auto;
+ text-wrap: nowrap;
+}
+
+.karavan .properties .headers .top .pf-v5-c-switch {
+ column-gap: 6px;
}
+
.karavan .properties .footer {
height: 100%;
display: contents;
diff --git
a/karavan-app/src/main/webui/src/designer/property/PropertiesHeader.tsx
b/karavan-app/src/main/webui/src/designer/property/PropertiesHeader.tsx
index b8aca6b4..00a3e1ee 100644
--- a/karavan-app/src/main/webui/src/designer/property/PropertiesHeader.tsx
+++ b/karavan-app/src/main/webui/src/designer/property/PropertiesHeader.tsx
@@ -25,13 +25,13 @@ import {
MenuToggle,
DropdownList,
DropdownItem, Flex, Popover, FlexItem, Badge, ClipboardCopy,
- Switch,
+ Switch, Radio, Tooltip,
} from '@patternfly/react-core';
import '../karavan.css';
import './DslProperties.css';
import "@patternfly/patternfly/patternfly.css";
import {CamelUi} from "../utils/CamelUi";
-import {useDesignerStore, useSelectorStore} from "../DesignerStore";
+import {useDesignerStore} from "../DesignerStore";
import {shallow} from "zustand/shallow";
import {usePropertiesHook} from "./usePropertiesHook";
import {CamelDisplayUtil} from "karavan-core/lib/api/CamelDisplayUtil";
@@ -56,7 +56,13 @@ export function PropertiesHeader(props: Props) {
const [isHeadersExpanded, setIsHeadersExpanded] = useState<boolean>(false);
const [isExchangePropertiesExpanded, setIsExchangePropertiesExpanded] =
useState<boolean>(false);
const [isMenuOpen, setMenuOpen] = useState<boolean>(false);
- const [isStepTypeOpen, setIsStepTypeOpen] = React.useState(false);
+ const [stepIsPoll, setStepIsPoll] = React.useState(false);
+ const [stepDynamic, setStepDynamic] = React.useState(false);
+
+ useEffect(() => {
+ setStepDynamic(selectedStep?.dslName === 'ToDynamicDefinition')
+ setStepIsPoll(selectedStep?.dslName === 'PollDefinition')
+ }, [])
useEffect(() => {
setMenuOpen(false)
@@ -68,67 +74,70 @@ export function PropertiesHeader(props: Props) {
const targetDslTitle = targetDsl?.replace("Definition", "");
const showMenu = hasSteps || targetDsl !== undefined;
return showMenu ?
- <Dropdown
- popperProps={{position: "end"}}
- isOpen={isMenuOpen}
- onSelect={() => {
- }}
- onOpenChange={(isOpen: boolean) => setMenuOpen(isOpen)}
- toggle={(toggleRef: React.Ref<MenuToggleElement>) => (
- <MenuToggle
- className="header-menu-toggle"
- ref={toggleRef}
- aria-label="menu"
- variant="plain"
- onClick={() => setMenuOpen(!isMenuOpen)}
- isExpanded={isMenuOpen}
- >
- <EllipsisVIcon/>
- </MenuToggle>
- )}
- >
- <DropdownList>
- {isFrom &&
- <DropdownItem key="changeFrom" onClick={(ev) => {
- ev.preventDefault()
- openSelectorToReplaceFrom((selectedStep as any).id)
- setMenuOpen(false);
- }}>
- Change From...
- </DropdownItem>}
- {hasSteps &&
- <DropdownItem key="saveStepsRoute" onClick={(ev) => {
- ev.preventDefault()
- if (selectedStep) {
- saveAsRoute(selectedStep, true);
- setMenuOpen(false);
- }
- }}>
- Save Steps to Route
- </DropdownItem>}
- {hasSteps && !isFrom &&
- <DropdownItem key="saveElementRoute" onClick={(ev) => {
- ev.preventDefault()
- if (selectedStep) {
- saveAsRoute(selectedStep, false);
+ <div style={{display: 'flex', flexDirection: 'row',
justifyContent: 'end', width: '100%'}}>
+ <Dropdown
+ popperProps={{position: "end"}}
+ isOpen={isMenuOpen}
+ onSelect={() => {
+ }}
+ onOpenChange={(isOpen: boolean) => setMenuOpen(isOpen)}
+ toggle={(toggleRef: React.Ref<MenuToggleElement>) => (
+ <MenuToggle
+ className="header-menu-toggle"
+ ref={toggleRef}
+ aria-label="menu"
+ variant="plain"
+ onClick={() => setMenuOpen(!isMenuOpen)}
+ isExpanded={isMenuOpen}
+ >
+ <EllipsisVIcon/>
+ </MenuToggle>
+ )}
+ >
+ <DropdownList>
+ {isFrom &&
+ <DropdownItem key="changeFrom" onClick={(ev) => {
+ ev.preventDefault()
+ openSelectorToReplaceFrom((selectedStep as
any).id)
setMenuOpen(false);
- }
- }}>
- Save Element to Route
- </DropdownItem>}
- {targetDsl &&
- <DropdownItem key="convert"
- onClick={(ev) => {
- ev.preventDefault()
- if (selectedStep) {
- convertStep(selectedStep,
targetDsl);
- setMenuOpen(false);
- }
- }}>
- Convert to {targetDslTitle}
- </DropdownItem>}
- </DropdownList>
- </Dropdown> : <></>;
+ }}>
+ Change From...
+ </DropdownItem>}
+ {hasSteps &&
+ <DropdownItem key="saveStepsRoute" onClick={(ev)
=> {
+ ev.preventDefault()
+ if (selectedStep) {
+ saveAsRoute(selectedStep, true);
+ setMenuOpen(false);
+ }
+ }}>
+ Save Steps to Route
+ </DropdownItem>}
+ {hasSteps && !isFrom &&
+ <DropdownItem key="saveElementRoute" onClick={(ev)
=> {
+ ev.preventDefault()
+ if (selectedStep) {
+ saveAsRoute(selectedStep, false);
+ setMenuOpen(false);
+ }
+ }}>
+ Save Element to Route
+ </DropdownItem>}
+ {targetDsl &&
+ <DropdownItem key="convert"
+ onClick={(ev) => {
+ ev.preventDefault()
+ if (selectedStep) {
+ convertStep(selectedStep,
targetDsl);
+ setMenuOpen(false);
+ }
+ }}>
+ Convert to {targetDslTitle}
+ </DropdownItem>}
+ </DropdownList>
+ </Dropdown>
+ </div>
+ : <></>;
}
function getExchangePropertiesSection(): React.JSX.Element {
@@ -138,35 +147,35 @@ export function PropertiesHeader(props: Props) {
isExpanded={isExchangePropertiesExpanded}>
<Flex className='component-headers' direction={{default:
"column"}}>
{exchangeProperties.map((header, index, array) =>
- <Flex key={index}>
- <ClipboardCopy key={index} hoverTip="Copy"
clickTip="Copied" variant="inline-compact"
- isCode>
- {header.name}
- </ClipboardCopy>
- <FlexItem align={{default: 'alignRight'}}>
- <Popover
- position={"left"}
- headerContent={header.name}
- bodyContent={header.description}
- footerContent={
- <Flex>
- <Text
component={TextVariants.p}>{header.javaType}</Text>
- <FlexItem align={{default:
'alignRight'}}>
- <Badge
isRead>{header.label}</Badge>
- </FlexItem>
- </Flex>
- }
- >
- <button type="button" aria-label="More
info" onClick={e => {
- e.preventDefault();
- e.stopPropagation();
- }}
className="pf-v5-c-form__group-label-help">
- <HelpIcon/>
- </button>
- </Popover>
- </FlexItem>
- </Flex>
- )}
+ <Flex key={index}>
+ <ClipboardCopy key={index} hoverTip="Copy"
clickTip="Copied" variant="inline-compact"
+ isCode>
+ {header.name}
+ </ClipboardCopy>
+ <FlexItem align={{default: 'alignRight'}}>
+ <Popover
+ position={"left"}
+ headerContent={header.name}
+ bodyContent={header.description}
+ footerContent={
+ <Flex>
+ <Text
component={TextVariants.p}>{header.javaType}</Text>
+ <FlexItem align={{default:
'alignRight'}}>
+ <Badge
isRead>{header.label}</Badge>
+ </FlexItem>
+ </Flex>
+ }
+ >
+ <button type="button" aria-label="More
info" onClick={e => {
+ e.preventDefault();
+ e.stopPropagation();
+ }}
className="pf-v5-c-form__group-label-help">
+ <HelpIcon/>
+ </button>
+ </Popover>
+ </FlexItem>
+ </Flex>
+ )}
</Flex>
</ExpandableSection>
)
@@ -235,24 +244,58 @@ export function PropertiesHeader(props: Props) {
const component = ComponentApi.findStepComponent(selectedStep);
const groups = (isFrom || isPoll) ? ['consumer', 'common'] : ['producer',
'common'];
const isKamelet = CamelUi.isKamelet(selectedStep);
- const isStepComponent = !isFrom && selectedStep !== undefined &&
!isKamelet && ['ToDefinition',
'PollDefinition'].includes(selectedStep?.dslName);
+ const isStepComponent = !isFrom && selectedStep !== undefined &&
!isKamelet && ['ToDefinition', 'PollDefinition',
'ToDynamicDefinition'].includes(selectedStep?.dslName);
+
+ function changeStepType(poll: boolean, dynamic: boolean) {
+ if (selectedStep) {
+ if (poll) {
+ convertStep(selectedStep, 'PollDefinition');
+ setStepIsPoll(true);
+ setStepDynamic(false);
+ } else if (dynamic) {
+ convertStep(selectedStep, 'ToDynamicDefinition');
+ setStepIsPoll(false);
+ setStepDynamic(true);
+ } else {
+ convertStep(selectedStep, 'ToDefinition');
+ setStepIsPoll(false);
+ setStepDynamic(false);
+ }
+ }
+ }
function getComponentStepTypeSwitch() {
- return (component?.component.producerOnly
- ? <></>
- : <Switch
- id="step-type-switch"
- label="Poll"
- isChecked={isStepTypeOpen}
- onChange={(event, checked) => {
- if (selectedStep) {
- convertStep(selectedStep, checked ? 'PollDefinition' :
'ToDefinition');
- setIsStepTypeOpen(checked);
- }
- }}
- ouiaId="step-type-switch"
- isReversed
- />
+ const pollSupported = !component?.component.producerOnly;
+ return (<div style={{display: 'flex', flexDirection: 'row',
justifyContent: 'end', width: '100%', gap: '10px'}}>
+ <Tooltip content='Send messages to a dynamic endpoint
evaluated on-demand' position='top-end'>
+ <Switch
+ id="step-type-dynamic"
+ label="Dynamic"
+ isChecked={stepDynamic}
+ onChange={(event, checked) => {
+ changeStepType(stepIsPoll, checked)
+ }}
+ ouiaId="step-type-switch"
+ isDisabled={stepIsPoll}
+ isReversed
+ />
+ </Tooltip>
+ {pollSupported &&
+ <Tooltip content='Simple Polling Consumer to obtain the
additional data' position='top-end'>
+ <Switch
+ id="step-type-poll"
+ label="Poll"
+ isChecked={stepIsPoll}
+ onChange={(event, checked) => {
+ changeStepType(checked, stepDynamic)
+ }}
+ ouiaId="step-type-switch"
+ isDisabled={stepDynamic}
+ isReversed
+ />
+ </Tooltip>
+ }
+ </div>
)
}
diff --git
a/karavan-app/src/main/webui/src/designer/property/usePropertiesHook.tsx
b/karavan-app/src/main/webui/src/designer/property/usePropertiesHook.tsx
index 2d806c0d..5c8e38b4 100644
--- a/karavan-app/src/main/webui/src/designer/property/usePropertiesHook.tsx
+++ b/karavan-app/src/main/webui/src/designer/property/usePropertiesHook.tsx
@@ -177,6 +177,7 @@ export function usePropertiesHook(designerType: 'routes' |
'rest' | 'beans' = 'r
}
const convertStep = (step: CamelElement, targetDslName: string) => {
+ console.log(targetDslName)
try {
// setSelectedStep(undefined);
if (targetDslName === 'ChoiceDefinition' && step.dslName ===
'FilterDefinition') {
diff --git a/karavan-app/src/main/webui/src/designer/route/DslConnections.css
b/karavan-app/src/main/webui/src/designer/route/DslConnections.css
index 03da5f2a..a75582ea 100644
--- a/karavan-app/src/main/webui/src/designer/route/DslConnections.css
+++ b/karavan-app/src/main/webui/src/designer/route/DslConnections.css
@@ -54,6 +54,15 @@
fill: transparent;
}
+.karavan .dsl-page .graph .connections .path-dynamic {
+ stroke-dasharray: 5;
+ -webkit-animation: dashdrawR 0.5s linear infinite;
+ animation: dashdrawR 0.5s linear infinite;
+ stroke: var(--pf-v5-global--Color--200);
+ stroke-width: 1;
+ fill: transparent;
+}
+
.karavan .dsl-page .graph .connections .path-poll {
stroke-dasharray: 5;
-webkit-animation: dashdrawL 0.5s linear infinite;
diff --git a/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx
b/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx
index 99fd0934..be404896 100644
--- a/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx
+++ b/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx
@@ -33,6 +33,7 @@ import {INTERNAL_COMPONENTS} from
"karavan-core/lib/api/ComponentApi";
const overlapGap: number = 40;
const DIAMETER: number = 34;
const RADIUS: number = DIAMETER / 2;
+type ConnectionType = 'internal' | 'remote' | 'nav' | 'poll' | 'dynamic';
export function DslConnections() {
@@ -83,10 +84,12 @@ export function DslConnections() {
}
}
- function getElementType(element: CamelElement): 'internal' | 'remote' |
'nav' | 'poll' {
+ function getElementType(element: CamelElement): ConnectionType {
const uri = (element as any).uri;
if (element.dslName === 'PollDefinition') {
return 'poll';
+ } else if (element.dslName === 'ToDynamicDefinition') {
+ return 'dynamic';
} else if (INTERNAL_COMPONENTS.includes((uri))) {
return 'nav';
} else {
@@ -95,8 +98,8 @@ export function DslConnections() {
}
}
- function getIncomings(): [string, number, 'internal' | 'remote' | 'nav' |
'poll'][] {
- let outs: [string, number, 'internal' | 'remote' | 'nav' | 'poll'][] =
Array.from(steps.values())
+ function getIncomings(): [string, number, ConnectionType][] {
+ let outs: [string, number, ConnectionType][] =
Array.from(steps.values())
.filter(pos => ["FromDefinition"].includes(pos.step.dslName))
.filter(pos => !(pos.step.dslName === 'FromDefinition' &&
(pos.step as any).uri === 'kamelet:source'))
.sort((pos1: DslPosition, pos2: DslPosition) => {
@@ -111,7 +114,7 @@ export function DslConnections() {
return outs;
}
- function getIncoming(data: [string, number, 'internal' | 'remote' | 'nav'
| 'poll']) {
+ function getIncoming(data: [string, number, ConnectionType]) {
const pos = steps.get(data[0]);
if (pos) {
const fromX = pos.headerRect.x + pos.headerRect.width / 2 - left;
@@ -144,7 +147,7 @@ export function DslConnections() {
// .filter(s => (s.step as any)?.parameters?.name === name)
// }
- function getIncomingIcons(data: [string, number, 'internal' | 'remote' |
'nav' | 'poll']) {
+ function getIncomingIcons(data: [string, number, ConnectionType]) {
const pos = steps.get(data[0]);
if (pos) {
const step = (pos.step as any);
@@ -186,7 +189,7 @@ export function DslConnections() {
}
}
- function hasOverlap(data: [string, number, 'internal' | 'remote' | 'nav' |
'poll'][]): boolean {
+ function hasOverlap(data: [string, number, ConnectionType][]): boolean {
let result = false;
data.forEach((d, i, arr) => {
if (i > 0 && d[1] - arr[i - 1][1] < overlapGap) result = true;
@@ -194,8 +197,8 @@ export function DslConnections() {
return result;
}
- function addGap(data: [string, number, 'internal' | 'remote' | 'nav' |
'poll'][]): [string, number, 'internal' | 'remote' | 'nav' | 'poll'][] {
- const result: [string, number, 'internal' | 'remote' | 'nav' |
'poll'][] = [];
+ function addGap(data: [string, number, ConnectionType][]): [string,
number, ConnectionType][] {
+ const result: [string, number, ConnectionType][] = [];
data.forEach((d, i, arr) => {
if (i > 0 && d[1] - arr[i - 1][1] < overlapGap) result.push([d[0],
d[1] + overlapGap, d[2]])
else result.push(d);
@@ -204,12 +207,12 @@ export function DslConnections() {
}
- function getOutgoings(): [string, number, 'internal' | 'remote' | 'nav' |
'poll'][] {
+ function getOutgoings(): [string, number, ConnectionType][] {
const outgoingDefinitions = TopologyUtils.getOutgoingDefinitions();
- let outs: [string, number, 'internal' | 'remote' | 'nav' | 'poll'][] =
Array.from(steps.values())
+ let outs: [string, number, ConnectionType][] =
Array.from(steps.values())
.filter(pos => outgoingDefinitions.includes(pos.step.dslName))
.filter(pos => pos.step.dslName !== 'KameletDefinition' ||
(pos.step.dslName === 'KameletDefinition' &&
!CamelUi.isActionKamelet(pos.step)))
- .filter(pos => ['ToDefinition',
'PollDefinition'].includes(pos.step.dslName) &&
!CamelUi.isActionKamelet(pos.step))
+ .filter(pos => ['ToDefinition', 'PollDefinition',
"ToDynamicDefinition"].includes(pos.step.dslName) &&
!CamelUi.isActionKamelet(pos.step))
.filter(pos => !CamelUi.isKameletSink(pos.step))
.sort((pos1: DslPosition, pos2: DslPosition) => {
const y1 = pos1.headerRect.y + pos1.headerRect.height / 2;
@@ -223,11 +226,12 @@ export function DslConnections() {
return outs;
}
- function getOutgoing(data: [string, number, 'internal' | 'remote' | 'nav'
| 'poll']) {
+ function getOutgoing(data: [string, number, ConnectionType]) {
const pos = steps.get(data[0]);
const isInternal = data[2] === 'internal';
const isNav = data[2] === 'nav';
const isPoll = data[2] === 'poll';
+ const isDynamic = data[2] === 'dynamic';
if (pos) {
const fromX = pos.headerRect.x + pos.headerRect.width / 2 - left;
const fromY = pos.headerRect.y + pos.headerRect.height / 2 - top;
@@ -244,7 +248,9 @@ export function DslConnections() {
const lineXi = lineX1 + 40;
const lineYi = lineY2;
- const className = isNav ? 'path-incoming-nav' : (isPoll ?
'path-poll' : 'path-incoming')
+ const className = isNav
+ ? 'path-incoming-nav'
+ : (isPoll ? 'path-poll' : (isDynamic ? 'path-dynamic'
:'path-incoming'))
return (!isInternal
? <g key={pos.step.uuid + "-outgoing"}>
@@ -258,7 +264,7 @@ export function DslConnections() {
}
}
- function getOutgoingIcons(data: [string, number, 'internal' | 'remote' |
'nav' | 'poll']) {
+ function getOutgoingIcons(data: [string, number, ConnectionType]) {
const pos = steps.get(data[0]);
if (pos) {
const step = (pos.step as any);
diff --git
a/karavan-app/src/main/webui/src/designer/route/element/DslElement.css
b/karavan-app/src/main/webui/src/designer/route/element/DslElement.css
index 785f667b..1b431837 100644
--- a/karavan-app/src/main/webui/src/designer/route/element/DslElement.css
+++ b/karavan-app/src/main/webui/src/designer/route/element/DslElement.css
@@ -128,6 +128,10 @@
border-radius: 33px;
}
+.karavan .step-element .header-icon-circle .dynamic{
+ fill: var(--pf-v5-global--Color--400);
+}
+
.karavan .step-element .header-icon-square {
border-radius: 33px;
}
diff --git a/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx
b/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx
index 045b154d..646f5ed9 100644
--- a/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx
+++ b/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx
@@ -728,7 +728,9 @@ export class CamelUi {
case 'AggregateDefinition':
return <AggregateIcon/>;
case 'ToDefinition':
- return <ToIcon/>;
+ return ToIcon();
+ case 'ToDynamicDefinition':
+ return ToIcon('dynamic');
case 'PollDefinition':
return <PollIcon/>;
case 'ChoiceDefinition' :
diff --git a/karavan-core/src/core/api/CamelDisplayUtil.ts
b/karavan-core/src/core/api/CamelDisplayUtil.ts
index d2078144..e103435b 100644
--- a/karavan-core/src/core/api/CamelDisplayUtil.ts
+++ b/karavan-core/src/core/api/CamelDisplayUtil.ts
@@ -33,7 +33,7 @@ export class CamelDisplayUtil {
} else if (element.dslName === 'RouteDefinition') {
const routeId = (element as RouteDefinition).id
return routeId ? routeId : CamelUtil.capitalizeName((element as
any).stepName);
- } else if ((element as any).uri && (['ToDefinition', 'FromDefinition',
'PollDefinition'].includes(element.dslName))) {
+ } else if ((element as any).uri && (['ToDefinition', 'FromDefinition',
'PollDefinition', 'ToDynamicDefinition'].includes(element.dslName))) {
const uri = (element as any).uri
return ComponentApi.getComponentTitleFromUri(uri) || '';
} else {
@@ -46,7 +46,7 @@ export class CamelDisplayUtil {
const kamelet: KameletModel | undefined =
CamelUtil.getKamelet(element);
if (kamelet) {
return kamelet.spec.definition.description;
- } else if ((element as any).uri && (['ToDefinition', 'FromDefinition',
'PollDefinition'].includes(element.dslName))) {
+ } else if ((element as any).uri && (['ToDefinition', 'FromDefinition',
'PollDefinition', 'ToDynamicDefinition'].includes(element.dslName))) {
const uri = (element as any).uri
return ComponentApi.getComponentDescriptionFromUri(uri) || '';
} else {
diff --git a/karavan-core/src/core/api/CamelUtil.ts
b/karavan-core/src/core/api/CamelUtil.ts
index 993bb306..5ee687a1 100644
--- a/karavan-core/src/core/api/CamelUtil.ts
+++ b/karavan-core/src/core/api/CamelUtil.ts
@@ -182,21 +182,16 @@ export class CamelUtil {
const uri: string = (element as any).uri;
const name = ComponentApi.getComponentNameFromUri(uri);
- if (dslName === 'ToDynamicDefinition') {
- const component = ComponentApi.findByName(dslName);
- return component ?
ComponentApi.getComponentProperties(component?.component.name, 'producer') : [];
+ if (name) {
+ const component = ComponentApi.findByName(name);
+ return component
+ ? ComponentApi.getComponentProperties(
+ component?.component.name,
+ element.dslName === 'FromDefinition' ? 'consumer' :
'producer',
+ )
+ : [];
} else {
- if (name) {
- const component = ComponentApi.findByName(name);
- return component
- ? ComponentApi.getComponentProperties(
- component?.component.name,
- element.dslName === 'FromDefinition' ? 'consumer' :
'producer',
- )
- : [];
- } else {
- return [];
- }
+ return [];
}
};
diff --git a/karavan-designer/src/designer/icons/EipIcons.tsx
b/karavan-designer/src/designer/icons/EipIcons.tsx
index fc928d9f..4bdac684 100644
--- a/karavan-designer/src/designer/icons/EipIcons.tsx
+++ b/karavan-designer/src/designer/icons/EipIcons.tsx
@@ -50,14 +50,14 @@ export function AggregateIcon() {
);
}
-export function ToIcon() {
+export function ToIcon(classname: string = '') {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={800}
height={800}
viewBox="0 0 32 32"
- className="icon"
+ className={classname ? "icon " + classname : "icon"}
>
<path d="m12.103 11.923 2.58 2.59H2.513v2h12.17l-2.58 2.59 1.41
1.41 5-5-5-5z"/>
<path
diff --git a/karavan-designer/src/designer/property/DslProperties.css
b/karavan-designer/src/designer/property/DslProperties.css
index 97b97a38..e2401cf3 100644
--- a/karavan-designer/src/designer/property/DslProperties.css
+++ b/karavan-designer/src/designer/property/DslProperties.css
@@ -24,14 +24,22 @@
width: 100%;
display: flex;
flex-direction: row;
+ /*justify-content: space-between;*/
+ gap: 10px;
}
.karavan .properties .headers .top h1 {
- width: 100%;
+ /*width: 100%;*/
margin-top: auto;
margin-bottom: auto;
+ text-wrap: nowrap;
+}
+
+.karavan .properties .headers .top .pf-v5-c-switch {
+ column-gap: 6px;
}
+
.karavan .properties .footer {
height: 100%;
display: contents;
diff --git a/karavan-designer/src/designer/property/PropertiesHeader.tsx
b/karavan-designer/src/designer/property/PropertiesHeader.tsx
index b8aca6b4..00a3e1ee 100644
--- a/karavan-designer/src/designer/property/PropertiesHeader.tsx
+++ b/karavan-designer/src/designer/property/PropertiesHeader.tsx
@@ -25,13 +25,13 @@ import {
MenuToggle,
DropdownList,
DropdownItem, Flex, Popover, FlexItem, Badge, ClipboardCopy,
- Switch,
+ Switch, Radio, Tooltip,
} from '@patternfly/react-core';
import '../karavan.css';
import './DslProperties.css';
import "@patternfly/patternfly/patternfly.css";
import {CamelUi} from "../utils/CamelUi";
-import {useDesignerStore, useSelectorStore} from "../DesignerStore";
+import {useDesignerStore} from "../DesignerStore";
import {shallow} from "zustand/shallow";
import {usePropertiesHook} from "./usePropertiesHook";
import {CamelDisplayUtil} from "karavan-core/lib/api/CamelDisplayUtil";
@@ -56,7 +56,13 @@ export function PropertiesHeader(props: Props) {
const [isHeadersExpanded, setIsHeadersExpanded] = useState<boolean>(false);
const [isExchangePropertiesExpanded, setIsExchangePropertiesExpanded] =
useState<boolean>(false);
const [isMenuOpen, setMenuOpen] = useState<boolean>(false);
- const [isStepTypeOpen, setIsStepTypeOpen] = React.useState(false);
+ const [stepIsPoll, setStepIsPoll] = React.useState(false);
+ const [stepDynamic, setStepDynamic] = React.useState(false);
+
+ useEffect(() => {
+ setStepDynamic(selectedStep?.dslName === 'ToDynamicDefinition')
+ setStepIsPoll(selectedStep?.dslName === 'PollDefinition')
+ }, [])
useEffect(() => {
setMenuOpen(false)
@@ -68,67 +74,70 @@ export function PropertiesHeader(props: Props) {
const targetDslTitle = targetDsl?.replace("Definition", "");
const showMenu = hasSteps || targetDsl !== undefined;
return showMenu ?
- <Dropdown
- popperProps={{position: "end"}}
- isOpen={isMenuOpen}
- onSelect={() => {
- }}
- onOpenChange={(isOpen: boolean) => setMenuOpen(isOpen)}
- toggle={(toggleRef: React.Ref<MenuToggleElement>) => (
- <MenuToggle
- className="header-menu-toggle"
- ref={toggleRef}
- aria-label="menu"
- variant="plain"
- onClick={() => setMenuOpen(!isMenuOpen)}
- isExpanded={isMenuOpen}
- >
- <EllipsisVIcon/>
- </MenuToggle>
- )}
- >
- <DropdownList>
- {isFrom &&
- <DropdownItem key="changeFrom" onClick={(ev) => {
- ev.preventDefault()
- openSelectorToReplaceFrom((selectedStep as any).id)
- setMenuOpen(false);
- }}>
- Change From...
- </DropdownItem>}
- {hasSteps &&
- <DropdownItem key="saveStepsRoute" onClick={(ev) => {
- ev.preventDefault()
- if (selectedStep) {
- saveAsRoute(selectedStep, true);
- setMenuOpen(false);
- }
- }}>
- Save Steps to Route
- </DropdownItem>}
- {hasSteps && !isFrom &&
- <DropdownItem key="saveElementRoute" onClick={(ev) => {
- ev.preventDefault()
- if (selectedStep) {
- saveAsRoute(selectedStep, false);
+ <div style={{display: 'flex', flexDirection: 'row',
justifyContent: 'end', width: '100%'}}>
+ <Dropdown
+ popperProps={{position: "end"}}
+ isOpen={isMenuOpen}
+ onSelect={() => {
+ }}
+ onOpenChange={(isOpen: boolean) => setMenuOpen(isOpen)}
+ toggle={(toggleRef: React.Ref<MenuToggleElement>) => (
+ <MenuToggle
+ className="header-menu-toggle"
+ ref={toggleRef}
+ aria-label="menu"
+ variant="plain"
+ onClick={() => setMenuOpen(!isMenuOpen)}
+ isExpanded={isMenuOpen}
+ >
+ <EllipsisVIcon/>
+ </MenuToggle>
+ )}
+ >
+ <DropdownList>
+ {isFrom &&
+ <DropdownItem key="changeFrom" onClick={(ev) => {
+ ev.preventDefault()
+ openSelectorToReplaceFrom((selectedStep as
any).id)
setMenuOpen(false);
- }
- }}>
- Save Element to Route
- </DropdownItem>}
- {targetDsl &&
- <DropdownItem key="convert"
- onClick={(ev) => {
- ev.preventDefault()
- if (selectedStep) {
- convertStep(selectedStep,
targetDsl);
- setMenuOpen(false);
- }
- }}>
- Convert to {targetDslTitle}
- </DropdownItem>}
- </DropdownList>
- </Dropdown> : <></>;
+ }}>
+ Change From...
+ </DropdownItem>}
+ {hasSteps &&
+ <DropdownItem key="saveStepsRoute" onClick={(ev)
=> {
+ ev.preventDefault()
+ if (selectedStep) {
+ saveAsRoute(selectedStep, true);
+ setMenuOpen(false);
+ }
+ }}>
+ Save Steps to Route
+ </DropdownItem>}
+ {hasSteps && !isFrom &&
+ <DropdownItem key="saveElementRoute" onClick={(ev)
=> {
+ ev.preventDefault()
+ if (selectedStep) {
+ saveAsRoute(selectedStep, false);
+ setMenuOpen(false);
+ }
+ }}>
+ Save Element to Route
+ </DropdownItem>}
+ {targetDsl &&
+ <DropdownItem key="convert"
+ onClick={(ev) => {
+ ev.preventDefault()
+ if (selectedStep) {
+ convertStep(selectedStep,
targetDsl);
+ setMenuOpen(false);
+ }
+ }}>
+ Convert to {targetDslTitle}
+ </DropdownItem>}
+ </DropdownList>
+ </Dropdown>
+ </div>
+ : <></>;
}
function getExchangePropertiesSection(): React.JSX.Element {
@@ -138,35 +147,35 @@ export function PropertiesHeader(props: Props) {
isExpanded={isExchangePropertiesExpanded}>
<Flex className='component-headers' direction={{default:
"column"}}>
{exchangeProperties.map((header, index, array) =>
- <Flex key={index}>
- <ClipboardCopy key={index} hoverTip="Copy"
clickTip="Copied" variant="inline-compact"
- isCode>
- {header.name}
- </ClipboardCopy>
- <FlexItem align={{default: 'alignRight'}}>
- <Popover
- position={"left"}
- headerContent={header.name}
- bodyContent={header.description}
- footerContent={
- <Flex>
- <Text
component={TextVariants.p}>{header.javaType}</Text>
- <FlexItem align={{default:
'alignRight'}}>
- <Badge
isRead>{header.label}</Badge>
- </FlexItem>
- </Flex>
- }
- >
- <button type="button" aria-label="More
info" onClick={e => {
- e.preventDefault();
- e.stopPropagation();
- }}
className="pf-v5-c-form__group-label-help">
- <HelpIcon/>
- </button>
- </Popover>
- </FlexItem>
- </Flex>
- )}
+ <Flex key={index}>
+ <ClipboardCopy key={index} hoverTip="Copy"
clickTip="Copied" variant="inline-compact"
+ isCode>
+ {header.name}
+ </ClipboardCopy>
+ <FlexItem align={{default: 'alignRight'}}>
+ <Popover
+ position={"left"}
+ headerContent={header.name}
+ bodyContent={header.description}
+ footerContent={
+ <Flex>
+ <Text
component={TextVariants.p}>{header.javaType}</Text>
+ <FlexItem align={{default:
'alignRight'}}>
+ <Badge
isRead>{header.label}</Badge>
+ </FlexItem>
+ </Flex>
+ }
+ >
+ <button type="button" aria-label="More
info" onClick={e => {
+ e.preventDefault();
+ e.stopPropagation();
+ }}
className="pf-v5-c-form__group-label-help">
+ <HelpIcon/>
+ </button>
+ </Popover>
+ </FlexItem>
+ </Flex>
+ )}
</Flex>
</ExpandableSection>
)
@@ -235,24 +244,58 @@ export function PropertiesHeader(props: Props) {
const component = ComponentApi.findStepComponent(selectedStep);
const groups = (isFrom || isPoll) ? ['consumer', 'common'] : ['producer',
'common'];
const isKamelet = CamelUi.isKamelet(selectedStep);
- const isStepComponent = !isFrom && selectedStep !== undefined &&
!isKamelet && ['ToDefinition',
'PollDefinition'].includes(selectedStep?.dslName);
+ const isStepComponent = !isFrom && selectedStep !== undefined &&
!isKamelet && ['ToDefinition', 'PollDefinition',
'ToDynamicDefinition'].includes(selectedStep?.dslName);
+
+ function changeStepType(poll: boolean, dynamic: boolean) {
+ if (selectedStep) {
+ if (poll) {
+ convertStep(selectedStep, 'PollDefinition');
+ setStepIsPoll(true);
+ setStepDynamic(false);
+ } else if (dynamic) {
+ convertStep(selectedStep, 'ToDynamicDefinition');
+ setStepIsPoll(false);
+ setStepDynamic(true);
+ } else {
+ convertStep(selectedStep, 'ToDefinition');
+ setStepIsPoll(false);
+ setStepDynamic(false);
+ }
+ }
+ }
function getComponentStepTypeSwitch() {
- return (component?.component.producerOnly
- ? <></>
- : <Switch
- id="step-type-switch"
- label="Poll"
- isChecked={isStepTypeOpen}
- onChange={(event, checked) => {
- if (selectedStep) {
- convertStep(selectedStep, checked ? 'PollDefinition' :
'ToDefinition');
- setIsStepTypeOpen(checked);
- }
- }}
- ouiaId="step-type-switch"
- isReversed
- />
+ const pollSupported = !component?.component.producerOnly;
+ return (<div style={{display: 'flex', flexDirection: 'row',
justifyContent: 'end', width: '100%', gap: '10px'}}>
+ <Tooltip content='Send messages to a dynamic endpoint
evaluated on-demand' position='top-end'>
+ <Switch
+ id="step-type-dynamic"
+ label="Dynamic"
+ isChecked={stepDynamic}
+ onChange={(event, checked) => {
+ changeStepType(stepIsPoll, checked)
+ }}
+ ouiaId="step-type-switch"
+ isDisabled={stepIsPoll}
+ isReversed
+ />
+ </Tooltip>
+ {pollSupported &&
+ <Tooltip content='Simple Polling Consumer to obtain the
additional data' position='top-end'>
+ <Switch
+ id="step-type-poll"
+ label="Poll"
+ isChecked={stepIsPoll}
+ onChange={(event, checked) => {
+ changeStepType(checked, stepDynamic)
+ }}
+ ouiaId="step-type-switch"
+ isDisabled={stepDynamic}
+ isReversed
+ />
+ </Tooltip>
+ }
+ </div>
)
}
diff --git a/karavan-designer/src/designer/property/usePropertiesHook.tsx
b/karavan-designer/src/designer/property/usePropertiesHook.tsx
index 2d806c0d..5c8e38b4 100644
--- a/karavan-designer/src/designer/property/usePropertiesHook.tsx
+++ b/karavan-designer/src/designer/property/usePropertiesHook.tsx
@@ -177,6 +177,7 @@ export function usePropertiesHook(designerType: 'routes' |
'rest' | 'beans' = 'r
}
const convertStep = (step: CamelElement, targetDslName: string) => {
+ console.log(targetDslName)
try {
// setSelectedStep(undefined);
if (targetDslName === 'ChoiceDefinition' && step.dslName ===
'FilterDefinition') {
diff --git a/karavan-designer/src/designer/route/DslConnections.css
b/karavan-designer/src/designer/route/DslConnections.css
index 03da5f2a..a75582ea 100644
--- a/karavan-designer/src/designer/route/DslConnections.css
+++ b/karavan-designer/src/designer/route/DslConnections.css
@@ -54,6 +54,15 @@
fill: transparent;
}
+.karavan .dsl-page .graph .connections .path-dynamic {
+ stroke-dasharray: 5;
+ -webkit-animation: dashdrawR 0.5s linear infinite;
+ animation: dashdrawR 0.5s linear infinite;
+ stroke: var(--pf-v5-global--Color--200);
+ stroke-width: 1;
+ fill: transparent;
+}
+
.karavan .dsl-page .graph .connections .path-poll {
stroke-dasharray: 5;
-webkit-animation: dashdrawL 0.5s linear infinite;
diff --git a/karavan-designer/src/designer/route/DslConnections.tsx
b/karavan-designer/src/designer/route/DslConnections.tsx
index 99fd0934..be404896 100644
--- a/karavan-designer/src/designer/route/DslConnections.tsx
+++ b/karavan-designer/src/designer/route/DslConnections.tsx
@@ -33,6 +33,7 @@ import {INTERNAL_COMPONENTS} from
"karavan-core/lib/api/ComponentApi";
const overlapGap: number = 40;
const DIAMETER: number = 34;
const RADIUS: number = DIAMETER / 2;
+type ConnectionType = 'internal' | 'remote' | 'nav' | 'poll' | 'dynamic';
export function DslConnections() {
@@ -83,10 +84,12 @@ export function DslConnections() {
}
}
- function getElementType(element: CamelElement): 'internal' | 'remote' |
'nav' | 'poll' {
+ function getElementType(element: CamelElement): ConnectionType {
const uri = (element as any).uri;
if (element.dslName === 'PollDefinition') {
return 'poll';
+ } else if (element.dslName === 'ToDynamicDefinition') {
+ return 'dynamic';
} else if (INTERNAL_COMPONENTS.includes((uri))) {
return 'nav';
} else {
@@ -95,8 +98,8 @@ export function DslConnections() {
}
}
- function getIncomings(): [string, number, 'internal' | 'remote' | 'nav' |
'poll'][] {
- let outs: [string, number, 'internal' | 'remote' | 'nav' | 'poll'][] =
Array.from(steps.values())
+ function getIncomings(): [string, number, ConnectionType][] {
+ let outs: [string, number, ConnectionType][] =
Array.from(steps.values())
.filter(pos => ["FromDefinition"].includes(pos.step.dslName))
.filter(pos => !(pos.step.dslName === 'FromDefinition' &&
(pos.step as any).uri === 'kamelet:source'))
.sort((pos1: DslPosition, pos2: DslPosition) => {
@@ -111,7 +114,7 @@ export function DslConnections() {
return outs;
}
- function getIncoming(data: [string, number, 'internal' | 'remote' | 'nav'
| 'poll']) {
+ function getIncoming(data: [string, number, ConnectionType]) {
const pos = steps.get(data[0]);
if (pos) {
const fromX = pos.headerRect.x + pos.headerRect.width / 2 - left;
@@ -144,7 +147,7 @@ export function DslConnections() {
// .filter(s => (s.step as any)?.parameters?.name === name)
// }
- function getIncomingIcons(data: [string, number, 'internal' | 'remote' |
'nav' | 'poll']) {
+ function getIncomingIcons(data: [string, number, ConnectionType]) {
const pos = steps.get(data[0]);
if (pos) {
const step = (pos.step as any);
@@ -186,7 +189,7 @@ export function DslConnections() {
}
}
- function hasOverlap(data: [string, number, 'internal' | 'remote' | 'nav' |
'poll'][]): boolean {
+ function hasOverlap(data: [string, number, ConnectionType][]): boolean {
let result = false;
data.forEach((d, i, arr) => {
if (i > 0 && d[1] - arr[i - 1][1] < overlapGap) result = true;
@@ -194,8 +197,8 @@ export function DslConnections() {
return result;
}
- function addGap(data: [string, number, 'internal' | 'remote' | 'nav' |
'poll'][]): [string, number, 'internal' | 'remote' | 'nav' | 'poll'][] {
- const result: [string, number, 'internal' | 'remote' | 'nav' |
'poll'][] = [];
+ function addGap(data: [string, number, ConnectionType][]): [string,
number, ConnectionType][] {
+ const result: [string, number, ConnectionType][] = [];
data.forEach((d, i, arr) => {
if (i > 0 && d[1] - arr[i - 1][1] < overlapGap) result.push([d[0],
d[1] + overlapGap, d[2]])
else result.push(d);
@@ -204,12 +207,12 @@ export function DslConnections() {
}
- function getOutgoings(): [string, number, 'internal' | 'remote' | 'nav' |
'poll'][] {
+ function getOutgoings(): [string, number, ConnectionType][] {
const outgoingDefinitions = TopologyUtils.getOutgoingDefinitions();
- let outs: [string, number, 'internal' | 'remote' | 'nav' | 'poll'][] =
Array.from(steps.values())
+ let outs: [string, number, ConnectionType][] =
Array.from(steps.values())
.filter(pos => outgoingDefinitions.includes(pos.step.dslName))
.filter(pos => pos.step.dslName !== 'KameletDefinition' ||
(pos.step.dslName === 'KameletDefinition' &&
!CamelUi.isActionKamelet(pos.step)))
- .filter(pos => ['ToDefinition',
'PollDefinition'].includes(pos.step.dslName) &&
!CamelUi.isActionKamelet(pos.step))
+ .filter(pos => ['ToDefinition', 'PollDefinition',
"ToDynamicDefinition"].includes(pos.step.dslName) &&
!CamelUi.isActionKamelet(pos.step))
.filter(pos => !CamelUi.isKameletSink(pos.step))
.sort((pos1: DslPosition, pos2: DslPosition) => {
const y1 = pos1.headerRect.y + pos1.headerRect.height / 2;
@@ -223,11 +226,12 @@ export function DslConnections() {
return outs;
}
- function getOutgoing(data: [string, number, 'internal' | 'remote' | 'nav'
| 'poll']) {
+ function getOutgoing(data: [string, number, ConnectionType]) {
const pos = steps.get(data[0]);
const isInternal = data[2] === 'internal';
const isNav = data[2] === 'nav';
const isPoll = data[2] === 'poll';
+ const isDynamic = data[2] === 'dynamic';
if (pos) {
const fromX = pos.headerRect.x + pos.headerRect.width / 2 - left;
const fromY = pos.headerRect.y + pos.headerRect.height / 2 - top;
@@ -244,7 +248,9 @@ export function DslConnections() {
const lineXi = lineX1 + 40;
const lineYi = lineY2;
- const className = isNav ? 'path-incoming-nav' : (isPoll ?
'path-poll' : 'path-incoming')
+ const className = isNav
+ ? 'path-incoming-nav'
+ : (isPoll ? 'path-poll' : (isDynamic ? 'path-dynamic'
:'path-incoming'))
return (!isInternal
? <g key={pos.step.uuid + "-outgoing"}>
@@ -258,7 +264,7 @@ export function DslConnections() {
}
}
- function getOutgoingIcons(data: [string, number, 'internal' | 'remote' |
'nav' | 'poll']) {
+ function getOutgoingIcons(data: [string, number, ConnectionType]) {
const pos = steps.get(data[0]);
if (pos) {
const step = (pos.step as any);
diff --git a/karavan-designer/src/designer/route/element/DslElement.css
b/karavan-designer/src/designer/route/element/DslElement.css
index 785f667b..1b431837 100644
--- a/karavan-designer/src/designer/route/element/DslElement.css
+++ b/karavan-designer/src/designer/route/element/DslElement.css
@@ -128,6 +128,10 @@
border-radius: 33px;
}
+.karavan .step-element .header-icon-circle .dynamic{
+ fill: var(--pf-v5-global--Color--400);
+}
+
.karavan .step-element .header-icon-square {
border-radius: 33px;
}
diff --git a/karavan-designer/src/designer/utils/CamelUi.tsx
b/karavan-designer/src/designer/utils/CamelUi.tsx
index 045b154d..646f5ed9 100644
--- a/karavan-designer/src/designer/utils/CamelUi.tsx
+++ b/karavan-designer/src/designer/utils/CamelUi.tsx
@@ -728,7 +728,9 @@ export class CamelUi {
case 'AggregateDefinition':
return <AggregateIcon/>;
case 'ToDefinition':
- return <ToIcon/>;
+ return ToIcon();
+ case 'ToDynamicDefinition':
+ return ToIcon('dynamic');
case 'PollDefinition':
return <PollIcon/>;
case 'ChoiceDefinition' :
diff --git a/karavan-space/src/designer/icons/EipIcons.tsx
b/karavan-space/src/designer/icons/EipIcons.tsx
index fc928d9f..4bdac684 100644
--- a/karavan-space/src/designer/icons/EipIcons.tsx
+++ b/karavan-space/src/designer/icons/EipIcons.tsx
@@ -50,14 +50,14 @@ export function AggregateIcon() {
);
}
-export function ToIcon() {
+export function ToIcon(classname: string = '') {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={800}
height={800}
viewBox="0 0 32 32"
- className="icon"
+ className={classname ? "icon " + classname : "icon"}
>
<path d="m12.103 11.923 2.58 2.59H2.513v2h12.17l-2.58 2.59 1.41
1.41 5-5-5-5z"/>
<path
diff --git a/karavan-space/src/designer/property/DslProperties.css
b/karavan-space/src/designer/property/DslProperties.css
index 97b97a38..e2401cf3 100644
--- a/karavan-space/src/designer/property/DslProperties.css
+++ b/karavan-space/src/designer/property/DslProperties.css
@@ -24,14 +24,22 @@
width: 100%;
display: flex;
flex-direction: row;
+ /*justify-content: space-between;*/
+ gap: 10px;
}
.karavan .properties .headers .top h1 {
- width: 100%;
+ /*width: 100%;*/
margin-top: auto;
margin-bottom: auto;
+ text-wrap: nowrap;
+}
+
+.karavan .properties .headers .top .pf-v5-c-switch {
+ column-gap: 6px;
}
+
.karavan .properties .footer {
height: 100%;
display: contents;
diff --git a/karavan-space/src/designer/property/PropertiesHeader.tsx
b/karavan-space/src/designer/property/PropertiesHeader.tsx
index b8aca6b4..00a3e1ee 100644
--- a/karavan-space/src/designer/property/PropertiesHeader.tsx
+++ b/karavan-space/src/designer/property/PropertiesHeader.tsx
@@ -25,13 +25,13 @@ import {
MenuToggle,
DropdownList,
DropdownItem, Flex, Popover, FlexItem, Badge, ClipboardCopy,
- Switch,
+ Switch, Radio, Tooltip,
} from '@patternfly/react-core';
import '../karavan.css';
import './DslProperties.css';
import "@patternfly/patternfly/patternfly.css";
import {CamelUi} from "../utils/CamelUi";
-import {useDesignerStore, useSelectorStore} from "../DesignerStore";
+import {useDesignerStore} from "../DesignerStore";
import {shallow} from "zustand/shallow";
import {usePropertiesHook} from "./usePropertiesHook";
import {CamelDisplayUtil} from "karavan-core/lib/api/CamelDisplayUtil";
@@ -56,7 +56,13 @@ export function PropertiesHeader(props: Props) {
const [isHeadersExpanded, setIsHeadersExpanded] = useState<boolean>(false);
const [isExchangePropertiesExpanded, setIsExchangePropertiesExpanded] =
useState<boolean>(false);
const [isMenuOpen, setMenuOpen] = useState<boolean>(false);
- const [isStepTypeOpen, setIsStepTypeOpen] = React.useState(false);
+ const [stepIsPoll, setStepIsPoll] = React.useState(false);
+ const [stepDynamic, setStepDynamic] = React.useState(false);
+
+ useEffect(() => {
+ setStepDynamic(selectedStep?.dslName === 'ToDynamicDefinition')
+ setStepIsPoll(selectedStep?.dslName === 'PollDefinition')
+ }, [])
useEffect(() => {
setMenuOpen(false)
@@ -68,67 +74,70 @@ export function PropertiesHeader(props: Props) {
const targetDslTitle = targetDsl?.replace("Definition", "");
const showMenu = hasSteps || targetDsl !== undefined;
return showMenu ?
- <Dropdown
- popperProps={{position: "end"}}
- isOpen={isMenuOpen}
- onSelect={() => {
- }}
- onOpenChange={(isOpen: boolean) => setMenuOpen(isOpen)}
- toggle={(toggleRef: React.Ref<MenuToggleElement>) => (
- <MenuToggle
- className="header-menu-toggle"
- ref={toggleRef}
- aria-label="menu"
- variant="plain"
- onClick={() => setMenuOpen(!isMenuOpen)}
- isExpanded={isMenuOpen}
- >
- <EllipsisVIcon/>
- </MenuToggle>
- )}
- >
- <DropdownList>
- {isFrom &&
- <DropdownItem key="changeFrom" onClick={(ev) => {
- ev.preventDefault()
- openSelectorToReplaceFrom((selectedStep as any).id)
- setMenuOpen(false);
- }}>
- Change From...
- </DropdownItem>}
- {hasSteps &&
- <DropdownItem key="saveStepsRoute" onClick={(ev) => {
- ev.preventDefault()
- if (selectedStep) {
- saveAsRoute(selectedStep, true);
- setMenuOpen(false);
- }
- }}>
- Save Steps to Route
- </DropdownItem>}
- {hasSteps && !isFrom &&
- <DropdownItem key="saveElementRoute" onClick={(ev) => {
- ev.preventDefault()
- if (selectedStep) {
- saveAsRoute(selectedStep, false);
+ <div style={{display: 'flex', flexDirection: 'row',
justifyContent: 'end', width: '100%'}}>
+ <Dropdown
+ popperProps={{position: "end"}}
+ isOpen={isMenuOpen}
+ onSelect={() => {
+ }}
+ onOpenChange={(isOpen: boolean) => setMenuOpen(isOpen)}
+ toggle={(toggleRef: React.Ref<MenuToggleElement>) => (
+ <MenuToggle
+ className="header-menu-toggle"
+ ref={toggleRef}
+ aria-label="menu"
+ variant="plain"
+ onClick={() => setMenuOpen(!isMenuOpen)}
+ isExpanded={isMenuOpen}
+ >
+ <EllipsisVIcon/>
+ </MenuToggle>
+ )}
+ >
+ <DropdownList>
+ {isFrom &&
+ <DropdownItem key="changeFrom" onClick={(ev) => {
+ ev.preventDefault()
+ openSelectorToReplaceFrom((selectedStep as
any).id)
setMenuOpen(false);
- }
- }}>
- Save Element to Route
- </DropdownItem>}
- {targetDsl &&
- <DropdownItem key="convert"
- onClick={(ev) => {
- ev.preventDefault()
- if (selectedStep) {
- convertStep(selectedStep,
targetDsl);
- setMenuOpen(false);
- }
- }}>
- Convert to {targetDslTitle}
- </DropdownItem>}
- </DropdownList>
- </Dropdown> : <></>;
+ }}>
+ Change From...
+ </DropdownItem>}
+ {hasSteps &&
+ <DropdownItem key="saveStepsRoute" onClick={(ev)
=> {
+ ev.preventDefault()
+ if (selectedStep) {
+ saveAsRoute(selectedStep, true);
+ setMenuOpen(false);
+ }
+ }}>
+ Save Steps to Route
+ </DropdownItem>}
+ {hasSteps && !isFrom &&
+ <DropdownItem key="saveElementRoute" onClick={(ev)
=> {
+ ev.preventDefault()
+ if (selectedStep) {
+ saveAsRoute(selectedStep, false);
+ setMenuOpen(false);
+ }
+ }}>
+ Save Element to Route
+ </DropdownItem>}
+ {targetDsl &&
+ <DropdownItem key="convert"
+ onClick={(ev) => {
+ ev.preventDefault()
+ if (selectedStep) {
+ convertStep(selectedStep,
targetDsl);
+ setMenuOpen(false);
+ }
+ }}>
+ Convert to {targetDslTitle}
+ </DropdownItem>}
+ </DropdownList>
+ </Dropdown>
+ </div>
+ : <></>;
}
function getExchangePropertiesSection(): React.JSX.Element {
@@ -138,35 +147,35 @@ export function PropertiesHeader(props: Props) {
isExpanded={isExchangePropertiesExpanded}>
<Flex className='component-headers' direction={{default:
"column"}}>
{exchangeProperties.map((header, index, array) =>
- <Flex key={index}>
- <ClipboardCopy key={index} hoverTip="Copy"
clickTip="Copied" variant="inline-compact"
- isCode>
- {header.name}
- </ClipboardCopy>
- <FlexItem align={{default: 'alignRight'}}>
- <Popover
- position={"left"}
- headerContent={header.name}
- bodyContent={header.description}
- footerContent={
- <Flex>
- <Text
component={TextVariants.p}>{header.javaType}</Text>
- <FlexItem align={{default:
'alignRight'}}>
- <Badge
isRead>{header.label}</Badge>
- </FlexItem>
- </Flex>
- }
- >
- <button type="button" aria-label="More
info" onClick={e => {
- e.preventDefault();
- e.stopPropagation();
- }}
className="pf-v5-c-form__group-label-help">
- <HelpIcon/>
- </button>
- </Popover>
- </FlexItem>
- </Flex>
- )}
+ <Flex key={index}>
+ <ClipboardCopy key={index} hoverTip="Copy"
clickTip="Copied" variant="inline-compact"
+ isCode>
+ {header.name}
+ </ClipboardCopy>
+ <FlexItem align={{default: 'alignRight'}}>
+ <Popover
+ position={"left"}
+ headerContent={header.name}
+ bodyContent={header.description}
+ footerContent={
+ <Flex>
+ <Text
component={TextVariants.p}>{header.javaType}</Text>
+ <FlexItem align={{default:
'alignRight'}}>
+ <Badge
isRead>{header.label}</Badge>
+ </FlexItem>
+ </Flex>
+ }
+ >
+ <button type="button" aria-label="More
info" onClick={e => {
+ e.preventDefault();
+ e.stopPropagation();
+ }}
className="pf-v5-c-form__group-label-help">
+ <HelpIcon/>
+ </button>
+ </Popover>
+ </FlexItem>
+ </Flex>
+ )}
</Flex>
</ExpandableSection>
)
@@ -235,24 +244,58 @@ export function PropertiesHeader(props: Props) {
const component = ComponentApi.findStepComponent(selectedStep);
const groups = (isFrom || isPoll) ? ['consumer', 'common'] : ['producer',
'common'];
const isKamelet = CamelUi.isKamelet(selectedStep);
- const isStepComponent = !isFrom && selectedStep !== undefined &&
!isKamelet && ['ToDefinition',
'PollDefinition'].includes(selectedStep?.dslName);
+ const isStepComponent = !isFrom && selectedStep !== undefined &&
!isKamelet && ['ToDefinition', 'PollDefinition',
'ToDynamicDefinition'].includes(selectedStep?.dslName);
+
+ function changeStepType(poll: boolean, dynamic: boolean) {
+ if (selectedStep) {
+ if (poll) {
+ convertStep(selectedStep, 'PollDefinition');
+ setStepIsPoll(true);
+ setStepDynamic(false);
+ } else if (dynamic) {
+ convertStep(selectedStep, 'ToDynamicDefinition');
+ setStepIsPoll(false);
+ setStepDynamic(true);
+ } else {
+ convertStep(selectedStep, 'ToDefinition');
+ setStepIsPoll(false);
+ setStepDynamic(false);
+ }
+ }
+ }
function getComponentStepTypeSwitch() {
- return (component?.component.producerOnly
- ? <></>
- : <Switch
- id="step-type-switch"
- label="Poll"
- isChecked={isStepTypeOpen}
- onChange={(event, checked) => {
- if (selectedStep) {
- convertStep(selectedStep, checked ? 'PollDefinition' :
'ToDefinition');
- setIsStepTypeOpen(checked);
- }
- }}
- ouiaId="step-type-switch"
- isReversed
- />
+ const pollSupported = !component?.component.producerOnly;
+ return (<div style={{display: 'flex', flexDirection: 'row',
justifyContent: 'end', width: '100%', gap: '10px'}}>
+ <Tooltip content='Send messages to a dynamic endpoint
evaluated on-demand' position='top-end'>
+ <Switch
+ id="step-type-dynamic"
+ label="Dynamic"
+ isChecked={stepDynamic}
+ onChange={(event, checked) => {
+ changeStepType(stepIsPoll, checked)
+ }}
+ ouiaId="step-type-switch"
+ isDisabled={stepIsPoll}
+ isReversed
+ />
+ </Tooltip>
+ {pollSupported &&
+ <Tooltip content='Simple Polling Consumer to obtain the
additional data' position='top-end'>
+ <Switch
+ id="step-type-poll"
+ label="Poll"
+ isChecked={stepIsPoll}
+ onChange={(event, checked) => {
+ changeStepType(checked, stepDynamic)
+ }}
+ ouiaId="step-type-switch"
+ isDisabled={stepDynamic}
+ isReversed
+ />
+ </Tooltip>
+ }
+ </div>
)
}
diff --git a/karavan-space/src/designer/property/usePropertiesHook.tsx
b/karavan-space/src/designer/property/usePropertiesHook.tsx
index 2d806c0d..5c8e38b4 100644
--- a/karavan-space/src/designer/property/usePropertiesHook.tsx
+++ b/karavan-space/src/designer/property/usePropertiesHook.tsx
@@ -177,6 +177,7 @@ export function usePropertiesHook(designerType: 'routes' |
'rest' | 'beans' = 'r
}
const convertStep = (step: CamelElement, targetDslName: string) => {
+ console.log(targetDslName)
try {
// setSelectedStep(undefined);
if (targetDslName === 'ChoiceDefinition' && step.dslName ===
'FilterDefinition') {
diff --git a/karavan-space/src/designer/route/DslConnections.css
b/karavan-space/src/designer/route/DslConnections.css
index 03da5f2a..a75582ea 100644
--- a/karavan-space/src/designer/route/DslConnections.css
+++ b/karavan-space/src/designer/route/DslConnections.css
@@ -54,6 +54,15 @@
fill: transparent;
}
+.karavan .dsl-page .graph .connections .path-dynamic {
+ stroke-dasharray: 5;
+ -webkit-animation: dashdrawR 0.5s linear infinite;
+ animation: dashdrawR 0.5s linear infinite;
+ stroke: var(--pf-v5-global--Color--200);
+ stroke-width: 1;
+ fill: transparent;
+}
+
.karavan .dsl-page .graph .connections .path-poll {
stroke-dasharray: 5;
-webkit-animation: dashdrawL 0.5s linear infinite;
diff --git a/karavan-space/src/designer/route/DslConnections.tsx
b/karavan-space/src/designer/route/DslConnections.tsx
index 99fd0934..be404896 100644
--- a/karavan-space/src/designer/route/DslConnections.tsx
+++ b/karavan-space/src/designer/route/DslConnections.tsx
@@ -33,6 +33,7 @@ import {INTERNAL_COMPONENTS} from
"karavan-core/lib/api/ComponentApi";
const overlapGap: number = 40;
const DIAMETER: number = 34;
const RADIUS: number = DIAMETER / 2;
+type ConnectionType = 'internal' | 'remote' | 'nav' | 'poll' | 'dynamic';
export function DslConnections() {
@@ -83,10 +84,12 @@ export function DslConnections() {
}
}
- function getElementType(element: CamelElement): 'internal' | 'remote' |
'nav' | 'poll' {
+ function getElementType(element: CamelElement): ConnectionType {
const uri = (element as any).uri;
if (element.dslName === 'PollDefinition') {
return 'poll';
+ } else if (element.dslName === 'ToDynamicDefinition') {
+ return 'dynamic';
} else if (INTERNAL_COMPONENTS.includes((uri))) {
return 'nav';
} else {
@@ -95,8 +98,8 @@ export function DslConnections() {
}
}
- function getIncomings(): [string, number, 'internal' | 'remote' | 'nav' |
'poll'][] {
- let outs: [string, number, 'internal' | 'remote' | 'nav' | 'poll'][] =
Array.from(steps.values())
+ function getIncomings(): [string, number, ConnectionType][] {
+ let outs: [string, number, ConnectionType][] =
Array.from(steps.values())
.filter(pos => ["FromDefinition"].includes(pos.step.dslName))
.filter(pos => !(pos.step.dslName === 'FromDefinition' &&
(pos.step as any).uri === 'kamelet:source'))
.sort((pos1: DslPosition, pos2: DslPosition) => {
@@ -111,7 +114,7 @@ export function DslConnections() {
return outs;
}
- function getIncoming(data: [string, number, 'internal' | 'remote' | 'nav'
| 'poll']) {
+ function getIncoming(data: [string, number, ConnectionType]) {
const pos = steps.get(data[0]);
if (pos) {
const fromX = pos.headerRect.x + pos.headerRect.width / 2 - left;
@@ -144,7 +147,7 @@ export function DslConnections() {
// .filter(s => (s.step as any)?.parameters?.name === name)
// }
- function getIncomingIcons(data: [string, number, 'internal' | 'remote' |
'nav' | 'poll']) {
+ function getIncomingIcons(data: [string, number, ConnectionType]) {
const pos = steps.get(data[0]);
if (pos) {
const step = (pos.step as any);
@@ -186,7 +189,7 @@ export function DslConnections() {
}
}
- function hasOverlap(data: [string, number, 'internal' | 'remote' | 'nav' |
'poll'][]): boolean {
+ function hasOverlap(data: [string, number, ConnectionType][]): boolean {
let result = false;
data.forEach((d, i, arr) => {
if (i > 0 && d[1] - arr[i - 1][1] < overlapGap) result = true;
@@ -194,8 +197,8 @@ export function DslConnections() {
return result;
}
- function addGap(data: [string, number, 'internal' | 'remote' | 'nav' |
'poll'][]): [string, number, 'internal' | 'remote' | 'nav' | 'poll'][] {
- const result: [string, number, 'internal' | 'remote' | 'nav' |
'poll'][] = [];
+ function addGap(data: [string, number, ConnectionType][]): [string,
number, ConnectionType][] {
+ const result: [string, number, ConnectionType][] = [];
data.forEach((d, i, arr) => {
if (i > 0 && d[1] - arr[i - 1][1] < overlapGap) result.push([d[0],
d[1] + overlapGap, d[2]])
else result.push(d);
@@ -204,12 +207,12 @@ export function DslConnections() {
}
- function getOutgoings(): [string, number, 'internal' | 'remote' | 'nav' |
'poll'][] {
+ function getOutgoings(): [string, number, ConnectionType][] {
const outgoingDefinitions = TopologyUtils.getOutgoingDefinitions();
- let outs: [string, number, 'internal' | 'remote' | 'nav' | 'poll'][] =
Array.from(steps.values())
+ let outs: [string, number, ConnectionType][] =
Array.from(steps.values())
.filter(pos => outgoingDefinitions.includes(pos.step.dslName))
.filter(pos => pos.step.dslName !== 'KameletDefinition' ||
(pos.step.dslName === 'KameletDefinition' &&
!CamelUi.isActionKamelet(pos.step)))
- .filter(pos => ['ToDefinition',
'PollDefinition'].includes(pos.step.dslName) &&
!CamelUi.isActionKamelet(pos.step))
+ .filter(pos => ['ToDefinition', 'PollDefinition',
"ToDynamicDefinition"].includes(pos.step.dslName) &&
!CamelUi.isActionKamelet(pos.step))
.filter(pos => !CamelUi.isKameletSink(pos.step))
.sort((pos1: DslPosition, pos2: DslPosition) => {
const y1 = pos1.headerRect.y + pos1.headerRect.height / 2;
@@ -223,11 +226,12 @@ export function DslConnections() {
return outs;
}
- function getOutgoing(data: [string, number, 'internal' | 'remote' | 'nav'
| 'poll']) {
+ function getOutgoing(data: [string, number, ConnectionType]) {
const pos = steps.get(data[0]);
const isInternal = data[2] === 'internal';
const isNav = data[2] === 'nav';
const isPoll = data[2] === 'poll';
+ const isDynamic = data[2] === 'dynamic';
if (pos) {
const fromX = pos.headerRect.x + pos.headerRect.width / 2 - left;
const fromY = pos.headerRect.y + pos.headerRect.height / 2 - top;
@@ -244,7 +248,9 @@ export function DslConnections() {
const lineXi = lineX1 + 40;
const lineYi = lineY2;
- const className = isNav ? 'path-incoming-nav' : (isPoll ?
'path-poll' : 'path-incoming')
+ const className = isNav
+ ? 'path-incoming-nav'
+ : (isPoll ? 'path-poll' : (isDynamic ? 'path-dynamic'
:'path-incoming'))
return (!isInternal
? <g key={pos.step.uuid + "-outgoing"}>
@@ -258,7 +264,7 @@ export function DslConnections() {
}
}
- function getOutgoingIcons(data: [string, number, 'internal' | 'remote' |
'nav' | 'poll']) {
+ function getOutgoingIcons(data: [string, number, ConnectionType]) {
const pos = steps.get(data[0]);
if (pos) {
const step = (pos.step as any);
diff --git a/karavan-space/src/designer/route/element/DslElement.css
b/karavan-space/src/designer/route/element/DslElement.css
index 785f667b..1b431837 100644
--- a/karavan-space/src/designer/route/element/DslElement.css
+++ b/karavan-space/src/designer/route/element/DslElement.css
@@ -128,6 +128,10 @@
border-radius: 33px;
}
+.karavan .step-element .header-icon-circle .dynamic{
+ fill: var(--pf-v5-global--Color--400);
+}
+
.karavan .step-element .header-icon-square {
border-radius: 33px;
}
diff --git a/karavan-space/src/designer/utils/CamelUi.tsx
b/karavan-space/src/designer/utils/CamelUi.tsx
index 045b154d..646f5ed9 100644
--- a/karavan-space/src/designer/utils/CamelUi.tsx
+++ b/karavan-space/src/designer/utils/CamelUi.tsx
@@ -728,7 +728,9 @@ export class CamelUi {
case 'AggregateDefinition':
return <AggregateIcon/>;
case 'ToDefinition':
- return <ToIcon/>;
+ return ToIcon();
+ case 'ToDynamicDefinition':
+ return ToIcon('dynamic');
case 'PollDefinition':
return <PollIcon/>;
case 'ChoiceDefinition' :