You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ma...@apache.org on 2023/08/28 17:11:18 UTC
[camel-karavan] 01/01: Fix #836
This is an automated email from the ASF dual-hosted git repository.
marat pushed a commit to branch feature-836
in repository https://gitbox.apache.org/repos/asf/camel-karavan.git
commit 23b792978c43d3cc49c8581bc62e5197838c3320
Author: Marat Gubaidullin <ma...@talismancloud.io>
AuthorDate: Mon Aug 28 13:10:52 2023 -0400
Fix #836
---
karavan-core/src/core/api/CamelDefinitionYaml.ts | 2 +-
karavan-core/src/core/api/CamelDisplayUtil.ts | 8 +-
karavan-core/src/core/api/CamelUtil.ts | 2 +-
.../src/core/model/IntegrationDefinition.ts | 2 +-
karavan-designer/package-lock.json | 64 +-
karavan-designer/package.json | 3 +-
karavan-designer/public/example/demo.camel.yaml | 74 +++
karavan-designer/src/App.tsx | 47 +-
karavan-designer/src/DesignerPage.tsx | 138 ++---
karavan-designer/src/designer/KaravanDesigner.tsx | 161 ++---
karavan-designer/src/designer/KaravanStore.ts | 244 ++++++++
karavan-designer/src/designer/MainToolbar.tsx | 2 +-
karavan-designer/src/designer/beans/BeanCard.tsx | 47 +-
.../src/designer/beans/BeanProperties.tsx | 174 +++---
.../src/designer/beans/BeansDesigner.tsx | 159 ++---
karavan-designer/src/designer/karavan.css | 51 +-
karavan-designer/src/designer/rest/RestCard.tsx | 93 ++-
.../src/designer/rest/RestConfigurationCard.tsx | 53 +-
.../src/designer/rest/RestDesigner.tsx | 281 ++++-----
.../src/designer/rest/RestMethodCard.tsx | 52 +-
.../src/designer/rest/RestMethodSelector.tsx | 48 +-
.../src/designer/route/DeleteConfirmation.tsx | 51 ++
.../src/designer/route/DslConnections.tsx | 245 ++++----
karavan-designer/src/designer/route/DslElement.tsx | 457 +++++++-------
.../src/designer/route/DslProperties.tsx | 223 +++----
.../src/designer/route/DslSelector.tsx | 217 +++----
.../src/designer/route/RouteDesigner.tsx | 238 +++----
.../src/designer/route/RouteDesignerLogic.tsx | 396 ------------
.../route/property/ComponentParameterField.tsx | 344 +++++------
.../designer/route/property/DataFormatField.tsx | 184 +++---
.../designer/route/property/DslPropertyField.tsx | 681 +++++++++++----------
.../designer/route/property/ExpressionField.tsx | 185 +++---
.../route/property/InfrastructureSelector.tsx | 129 ++--
.../route/property/KameletPropertyField.tsx | 180 +++---
.../src/designer/route/property/ModalEditor.tsx | 97 ++-
.../src/designer/route/property/ObjectField.tsx | 84 +--
.../route/useDrawerMutationsObserver.tsx} | 31 +-
.../src/designer/route/usePropertiesHook.tsx | 128 ++++
.../route/useResizeObserver.tsx} | 31 +-
.../src/designer/route/useRouteDesignerHook.tsx | 302 +++++++++
.../src/designer/templates/TemplatesDesigner.tsx | 71 ---
karavan-designer/src/designer/utils/CamelUi.tsx | 26 +-
karavan-designer/src/designer/utils/EventBus.ts | 16 +-
.../src/designer/utils/InfrastructureAPI.ts | 34 +
.../src/designer/utils/IntegrationHeader.tsx | 41 ++
.../src/designer/utils/KaravanComponents.tsx | 44 --
.../src/designer/utils/KaravanIcons.tsx | 217 +++++--
karavan-designer/src/index.css | 6 -
karavan-designer/src/index.tsx | 8 +-
49 files changed, 3314 insertions(+), 3057 deletions(-)
diff --git a/karavan-core/src/core/api/CamelDefinitionYaml.ts b/karavan-core/src/core/api/CamelDefinitionYaml.ts
index 073b1a5f..ba086ef7 100644
--- a/karavan-core/src/core/api/CamelDefinitionYaml.ts
+++ b/karavan-core/src/core/api/CamelDefinitionYaml.ts
@@ -68,7 +68,7 @@ export class CamelDefinitionYaml {
}
delete object.uuid;
- delete object.show;
+ delete object.showChildren;
for (const [key, value] of Object.entries(object) as [string, any][]) {
if (value instanceof CamelElement || (typeof value === 'object' && value?.dslName)) {
diff --git a/karavan-core/src/core/api/CamelDisplayUtil.ts b/karavan-core/src/core/api/CamelDisplayUtil.ts
index 887099ef..eee33ccb 100644
--- a/karavan-core/src/core/api/CamelDisplayUtil.ts
+++ b/karavan-core/src/core/api/CamelDisplayUtil.ts
@@ -75,13 +75,9 @@ export class CamelDisplayUtil {
return clone;
};
- static setElementVisibility = (
- step: CamelElement,
- showChildren: boolean,
- expandedUuids: string[],
- ): CamelElement => {
+ static setElementVisibility = (step: CamelElement, showChildren: boolean, expandedUuids: string[]): CamelElement => {
const result = CamelDefinitionApi.createStep(step.dslName, step);
- result.show = showChildren;
+ result.showChildren = showChildren;
if (result.dslName === 'StepDefinition') {
showChildren = expandedUuids.includes(result.uuid);
}
diff --git a/karavan-core/src/core/api/CamelUtil.ts b/karavan-core/src/core/api/CamelUtil.ts
index 791ec0fa..65579d59 100644
--- a/karavan-core/src/core/api/CamelUtil.ts
+++ b/karavan-core/src/core/api/CamelUtil.ts
@@ -209,7 +209,7 @@ export class CamelUtil {
result[1].push(`${property.displayName} is required`);
} else if (property.type === 'ExpressionDefinition') {
const expressionMeta = CamelMetadataApi.getCamelModelMetadataByClassName('ExpressionDefinition');
- const expressionCheck = expressionMeta?.properties.some(ep => {
+ const expressionCheck = expressionMeta && value!== undefined && expressionMeta?.properties.some(ep => {
const expValue = value[ep.name];
if (expValue) {
const checkedExpression = CamelUtil.checkRequired(expValue);
diff --git a/karavan-core/src/core/model/IntegrationDefinition.ts b/karavan-core/src/core/model/IntegrationDefinition.ts
index 1d2519b3..3af3c6c5 100644
--- a/karavan-core/src/core/model/IntegrationDefinition.ts
+++ b/karavan-core/src/core/model/IntegrationDefinition.ts
@@ -52,7 +52,7 @@ export class Integration {
export class CamelElement {
uuid: string = '';
dslName: string = '';
- show: boolean = true;
+ showChildren: boolean = true;
constructor(dslName: string) {
this.uuid = uuidv4();
diff --git a/karavan-designer/package-lock.json b/karavan-designer/package-lock.json
index a9956967..d6fbc8b5 100644
--- a/karavan-designer/package-lock.json
+++ b/karavan-designer/package-lock.json
@@ -9,7 +9,7 @@
"version": "4.0.0-RC2",
"license": "Apache-2.0",
"dependencies": {
- "@monaco-editor/react": "4.5.0",
+ "@monaco-editor/react": "4.5.1",
"@patternfly/patternfly": "^5.0.2",
"@patternfly/react-core": "^5.0.0",
"@patternfly/react-table": "^5.0.0",
@@ -24,7 +24,8 @@
"react": "18.2.0",
"react-dom": "18.2.0",
"rxjs": "7.8.1",
- "uuid": "9.0.0"
+ "uuid": "9.0.0",
+ "zustand": "^4.4.1"
},
"devDependencies": {
"@svgr/webpack": "^7.0.0",
@@ -36,7 +37,7 @@
"@typescript-eslint/eslint-plugin": "^5.59.2",
"@typescript-eslint/parser": "^5.59.2",
"eslint": "^8.39.0",
- "monaco-editor": "0.38.0",
+ "monaco-editor": "0.41.0",
"react-scripts": "5.0.1",
"typescript": "^4.9.5"
}
@@ -3198,9 +3199,9 @@
}
},
"node_modules/@monaco-editor/react": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.5.0.tgz",
- "integrity": "sha512-VJMkp5Fe1+w8pLEq8tZPHZKu8zDXQIA1FtiDTSNccg1D3wg1YIZaH2es2Qpvop1k62g3c/YySRb3bnGXu2XwYQ==",
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.5.1.tgz",
+ "integrity": "sha512-NNDFdP+2HojtNhCkRfE6/D6ro6pBNihaOzMbGK84lNWzRu+CfBjwzGt4jmnqimLuqp5yE5viHS2vi+QOAnD5FQ==",
"dependencies": {
"@monaco-editor/loader": "^1.3.3"
},
@@ -3996,7 +3997,7 @@
"version": "15.7.5",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==",
- "dev": true
+ "devOptional": true
},
"node_modules/@types/qs": {
"version": "6.9.7",
@@ -4014,7 +4015,7 @@
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.0.tgz",
"integrity": "sha512-0FLj93y5USLHdnhIhABk83rm8XEGA7kH3cr+YUlvxoUGp1xNt/DINUMvqPxLyOQMzLmZe8i4RTHbvb8MC7NmrA==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
@@ -4049,7 +4050,7 @@
"version": "0.16.3",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
"integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==",
- "dev": true
+ "devOptional": true
},
"node_modules/@types/semver": {
"version": "7.3.13",
@@ -6530,7 +6531,7 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
"integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==",
- "dev": true
+ "devOptional": true
},
"node_modules/dagre": {
"version": "0.8.5",
@@ -9388,7 +9389,7 @@
"version": "9.0.21",
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
"integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==",
- "dev": true,
+ "devOptional": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/immer"
@@ -12777,9 +12778,9 @@
}
},
"node_modules/monaco-editor": {
- "version": "0.38.0",
- "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.38.0.tgz",
- "integrity": "sha512-11Fkh6yzEmwx7O0YoLxeae0qEGFwmyPRlVxpg7oF9czOOCB/iCjdJrG5I67da5WiXK3YJCxoz9TJFE8Tfq/v9A=="
+ "version": "0.41.0",
+ "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.41.0.tgz",
+ "integrity": "sha512-1o4olnZJsiLmv5pwLEAmzHTE/5geLKQ07BrGxlF4Ri/AXAc2yyDGZwHjiTqD8D/ROKUZmwMA28A+yEowLNOEcA=="
},
"node_modules/ms": {
"version": "2.1.2",
@@ -17384,6 +17385,14 @@
"requires-port": "^1.0.0"
}
},
+ "node_modules/use-sync-external-store": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
+ "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -18430,6 +18439,33 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/zustand": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.4.1.tgz",
+ "integrity": "sha512-QCPfstAS4EBiTQzlaGP1gmorkh/UL1Leaj2tdj+zZCZ/9bm0WS7sI2wnfD5lpOszFqWJ1DcPnGoY8RDL61uokw==",
+ "dependencies": {
+ "use-sync-external-store": "1.2.0"
+ },
+ "engines": {
+ "node": ">=12.7.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.8",
+ "immer": ">=9.0",
+ "react": ">=16.8"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ }
+ }
}
}
}
diff --git a/karavan-designer/package.json b/karavan-designer/package.json
index 548304dd..a89a6b7e 100644
--- a/karavan-designer/package.json
+++ b/karavan-designer/package.json
@@ -41,7 +41,8 @@
"react": "18.2.0",
"react-dom": "18.2.0",
"rxjs": "7.8.1",
- "uuid": "9.0.0"
+ "uuid": "9.0.0",
+ "zustand": "^4.4.1"
},
"devDependencies": {
"@svgr/webpack": "^7.0.0",
diff --git a/karavan-designer/public/example/demo.camel.yaml b/karavan-designer/public/example/demo.camel.yaml
new file mode 100644
index 00000000..46692463
--- /dev/null
+++ b/karavan-designer/public/example/demo.camel.yaml
@@ -0,0 +1,74 @@
+- route:
+ id: route-f435
+ from:
+ uri: kamelet:timer-source
+ id: from-e52c
+ steps:
+ - filter:
+ expression: {}
+ id: filter-064f
+# - choice:
+# when:
+# - expression: {}
+# id: when-064f
+# steps:
+# - multicast:
+# id: multicast-38cea
+# steps:
+# - bean:
+# id: bean-3b8e
+# - log:
+# message: ${body}
+# id: log-546f
+# - loop:
+# expression: {}
+# id: loop-4635
+# steps:
+# - convertBodyTo:
+# id: convertBodyTo-1cae
+# otherwise:
+# id: otherwise-0b09
+# steps:
+# - filter:
+# expression: {}
+# id: filter-a02b
+# id: choice-c53c
+# - doTry:
+# id: doTry-8fd5
+# doCatch:
+# - id: doCatch-1071
+# - id: doCatch-c38e
+# steps:
+# - routingSlip:
+# expression: {}
+# id: routingSlip-a85a
+#- route:
+# id: route-178a
+# from:
+# uri: kamelet:aws-cloudtrail-source
+# id: from-3e7d
+# steps:
+# - multicast:
+# id: multicast-eef7
+# steps:
+# - bean:
+# id: bean-a5ef
+# - aggregate:
+# id: aggregate-f5d8
+# - aggregate:
+# id: aggregate-b9e7
+# - aggregate:
+# id: aggregate-5eb8
+# - aggregate:
+# id: aggregate-c57e
+# - aggregate:
+# id: aggregate-1cd4
+# - bean:
+# id: bean-72a1
+# - choice:
+# when:
+# - expression: {}
+# id: when-a56b
+# otherwise:
+# id: otherwise-9f31
+# id: choice-1905
diff --git a/karavan-designer/src/App.tsx b/karavan-designer/src/App.tsx
index b9cae192..02476d89 100644
--- a/karavan-designer/src/App.tsx
+++ b/karavan-designer/src/App.tsx
@@ -18,8 +18,8 @@ import * as React from "react";
import {
Alert,
AlertActionCloseButton, AlertGroup,
- Bullseye, Button, Divider, Flex, FlexItem,
- Page, Spinner, Tooltip,
+ Bullseye, Button, Divider, Flex, FlexItem, Masthead, MastheadBrand, MastheadContent, MastheadMain, MastheadToggle,
+ Page, PageSidebar, PageSidebarBody, PageToggleButton, Spinner, Tooltip,
} from "@patternfly/react-core";
import {KameletApi} from "karavan-core/lib/api/KameletApi";
import {ComponentApi} from "karavan-core/lib/api/ComponentApi";
@@ -94,7 +94,8 @@ class App extends React.Component<Props, State> {
fetch("kamelets/kamelets.yaml"),
fetch("components/components.json"),
fetch("snippets/org.apache.camel.AggregationStrategy"),
- fetch("snippets/org.apache.camel.Processor")
+ fetch("snippets/org.apache.camel.Processor"),
+ fetch("example/demo.camel.yaml")
// fetch("components/supported-components.json"),
]).then(responses =>
Promise.all(responses.map(response => response.text()))
@@ -113,6 +114,10 @@ class App extends React.Component<Props, State> {
TemplateApi.saveTemplate("org.apache.camel.Processor", data[3]);
if (data[4]) {
+ this.setState({yaml: data[4], name: "demo.camel.yaml"})
+ }
+
+ if (data[5]) {
ComponentApi.saveSupportedComponents(data[4]);
ComponentApi.setSupportedOnly(true);
}
@@ -122,8 +127,7 @@ class App extends React.Component<Props, State> {
}
save(filename: string, yaml: string, propertyOnly: boolean) {
- this.setState({name: filename, yaml: yaml});
- // console.log(yaml);
+ console.log(yaml);
}
getSpinner() {
@@ -183,6 +187,17 @@ class App extends React.Component<Props, State> {
}
}
+ getHeader = () => (
+ <Masthead>
+ </Masthead>
+ );
+
+ getSidebar = () => (
+ <PageSidebar isSidebarOpen={true} id="fill-sidebar">
+ <PageSidebarBody>Navigation</PageSidebarBody>
+ </PageSidebar>
+ );
+
public render() {
const {loaded} = this.state;
return (
@@ -197,18 +212,16 @@ class App extends React.Component<Props, State> {
</Alert>
))}
</AlertGroup>
- <>
- <Flex direction={{default: "row"}} style={{width: "100%", height: "100%"}}
- alignItems={{default: "alignItemsStretch"}} spaceItems={{default: 'spaceItemsNone'}}>
- <FlexItem>
- {this.pageNav()}
- </FlexItem>
- <FlexItem flex={{default: "flex_2"}} style={{height: "100%"}}>
- {loaded !== true && this.getSpinner()}
- {loaded === true && this.getPage()}
- </FlexItem>
- </Flex>
- </>
+ <Flex direction={{default: "row"}} style={{width: "100%", height: "100%"}}
+ alignItems={{default: "alignItemsStretch"}} spaceItems={{default: 'spaceItemsNone'}}>
+ <FlexItem>
+ {this.pageNav()}
+ </FlexItem>
+ <FlexItem flex={{default: "flex_2"}} style={{height: "100%"}}>
+ {loaded !== true && this.getSpinner()}
+ {loaded === true && this.getPage()}
+ </FlexItem>
+ </Flex>
</Page>
)
}
diff --git a/karavan-designer/src/DesignerPage.tsx b/karavan-designer/src/DesignerPage.tsx
index 9bcc0a2e..8b6b380b 100644
--- a/karavan-designer/src/DesignerPage.tsx
+++ b/karavan-designer/src/DesignerPage.tsx
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import React from 'react';
+import React, {useEffect, useState} from 'react';
import {
Toolbar,
ToolbarContent,
@@ -26,7 +26,8 @@ import DownloadIcon from "@patternfly/react-icons/dist/esm/icons/download-icon";
import DownloadImageIcon from "@patternfly/react-icons/dist/esm/icons/image-icon";
import {KaravanDesigner} from "./designer/KaravanDesigner";
import Editor from "@monaco-editor/react";
-import {EventBus} from "./designer/utils/EventBus";
+import {EventBus, IntegrationUpdate} from "./designer/utils/EventBus";
+import {InfrastructureAPI} from "./designer/utils/InfrastructureAPI";
interface Props {
name: string,
@@ -35,25 +36,23 @@ interface Props {
onSave: (filename: string, yaml: string, propertyOnly: boolean) => void
}
-interface State {
- mode: "design" | "code",
-}
-
-export class DesignerPage extends React.Component<Props, State> {
+export const DesignerPage = (props: Props) => {
- public state: State = {
- mode: 'design',
- };
+ const [mode, setMode] = useState<"design" | "code">('design');
+ const [yaml, setYaml] = useState<string>(props.yaml);
- componentDidMount() {
- }
+ useEffect(() => {
+ console.log("DesignerPage")
+ // setYaml();
+ }, []);
- save(filename: string, yaml: string, propertyOnly: boolean) {
- this.props.onSave?.call(this, filename, yaml, propertyOnly);
+ function save(filename: string, yaml: string, propertyOnly: boolean) {
+ setYaml(yaml);
+ props.onSave(filename, yaml, propertyOnly);
}
- download = () => {
- const {name, yaml} = this.props;
+ function download () {
+ const {name, yaml} = props;
if (name && yaml) {
const a = document.createElement('a');
a.setAttribute('download', 'example.yaml');
@@ -62,18 +61,17 @@ export class DesignerPage extends React.Component<Props, State> {
}
}
- downloadImage = () => {
+ function downloadImage () {
EventBus.sendCommand("downloadImage");
}
- getDesigner = () => {
- const {name, yaml} = this.props;
+ function getDesigner () {
return (
<KaravanDesigner
- dark={this.props.dark}
- filename={name}
+ dark={props.dark}
+ filename={props.name}
yaml={yaml}
- onSave={(filename, yaml, propertyOnly) => this.save(filename, yaml, propertyOnly)}
+ onSave={(filename, yaml, propertyOnly) => save(filename, yaml, propertyOnly)}
onGetCustomCode={name => {
return new Promise<string | undefined>(resolve => resolve(undefined))
}}
@@ -84,8 +82,7 @@ export class DesignerPage extends React.Component<Props, State> {
)
}
- getEditor = () => {
- const {name, yaml} = this.props;
+ function getEditor () {
return (
<Editor
height="100vh"
@@ -95,58 +92,55 @@ export class DesignerPage extends React.Component<Props, State> {
className={'code-editor'}
onChange={(value, ev) => {
if (value) {
- this.save(name, value, false)
+ save(props.name, value, false)
}
}}
/>
)
}
- render() {
- const {mode} = this.state;
- return (
- <PageSection className="kamelet-section designer-page" padding={{default: 'noPadding'}}>
- <PageSection className="tools-section" padding={{default: 'noPadding'}}
- style={{backgroundColor:"transparent", paddingLeft: "var(--pf-v5-c-page__main-section--PaddingLeft)"}}>
- <Flex className="tools" justifyContent={{default: 'justifyContentSpaceBetween'}}>
- <FlexItem>
- <TextContent className="header">
- <Text component="h2">Designer</Text>
- </TextContent>
- </FlexItem>
- <FlexItem>
- <Toolbar id="toolbar-group-types">
- <ToolbarContent>
- <ToolbarItem>
- <ToggleGroup>
- <ToggleGroupItem text="Design" buttonId="design" isSelected={mode === "design"}
- onChange={(_event, s) => this.setState({mode: "design"})} />
- <ToggleGroupItem text="Code" buttonId="code" isSelected={mode === "code"}
- onChange={(_event, s) => this.setState({mode: "code"})} />
- </ToggleGroup>
- </ToolbarItem>
- <ToolbarItem>
- <Tooltip content="Download YAML" position={"bottom"}>
- <Button variant="primary" icon={<DownloadIcon/>} onClick={e => this.download()}>
- YAML
- </Button>
- </Tooltip>
- </ToolbarItem>
- <ToolbarItem>
- <Tooltip content="Download image" position={"bottom"}>
- <Button variant="secondary" icon={<DownloadImageIcon/>} onClick={e => this.downloadImage()}>
- Image
- </Button>
- </Tooltip>
- </ToolbarItem>
- </ToolbarContent>
- </Toolbar>
- </FlexItem>
- </Flex>
- </PageSection>
- {mode === 'design' && this.getDesigner()}
- {mode === 'code' && this.getEditor()}
- </PageSection>
- );
- }
+ return (
+ <PageSection className="designer-page" padding={{default: 'noPadding'}}>
+ <div className="tools-section" //padding={{default: 'noPadding'}}
+ style={{backgroundColor:"transparent", paddingLeft: "var(--pf-v5-c-page__main-section--PaddingLeft)"}}>
+ <Flex className="tools" justifyContent={{default: 'justifyContentSpaceBetween'}}>
+ <FlexItem>
+ <TextContent className="header">
+ <Text component="h2">Designer</Text>
+ </TextContent>
+ </FlexItem>
+ <FlexItem>
+ <Toolbar id="toolbar-group-types">
+ <ToolbarContent>
+ <ToolbarItem>
+ <ToggleGroup>
+ <ToggleGroupItem text="Design" buttonId="design" isSelected={mode === "design"}
+ onChange={(_event, s) => setMode("design")} />
+ <ToggleGroupItem text="Code" buttonId="code" isSelected={mode === "code"}
+ onChange={(_event, s) => setMode("code")} />
+ </ToggleGroup>
+ </ToolbarItem>
+ <ToolbarItem>
+ <Tooltip content="Download YAML" position={"bottom"}>
+ <Button variant="primary" icon={<DownloadIcon/>} onClick={e => download()}>
+ YAML
+ </Button>
+ </Tooltip>
+ </ToolbarItem>
+ <ToolbarItem>
+ <Tooltip content="Download image" position={"bottom"}>
+ <Button variant="secondary" icon={<DownloadImageIcon/>} onClick={e => downloadImage()}>
+ Image
+ </Button>
+ </Tooltip>
+ </ToolbarItem>
+ </ToolbarContent>
+ </Toolbar>
+ </FlexItem>
+ </Flex>
+ </div>
+ {mode === 'design' && getDesigner()}
+ {mode === 'code' && getEditor()}
+ </PageSection>
+ )
};
\ No newline at end of file
diff --git a/karavan-designer/src/designer/KaravanDesigner.tsx b/karavan-designer/src/designer/KaravanDesigner.tsx
index a2556b2a..b101edd7 100644
--- a/karavan-designer/src/designer/KaravanDesigner.tsx
+++ b/karavan-designer/src/designer/KaravanDesigner.tsx
@@ -14,10 +14,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import React from 'react';
+import React, {useEffect, useState} from 'react';
import {
Badge,
- PageSection, PageSectionVariants, Tab, Tabs, TabTitleIcon, TabTitleText, Tooltip,
+ PageSection,
+ PageSectionVariants,
+ Switch,
+ Tab,
+ Tabs,
+ TabTitleIcon, TabTitleText,
+ Tooltip,
} from '@patternfly/react-core';
import './karavan.css';
import {RouteDesigner} from "./route/RouteDesigner";
@@ -25,9 +31,13 @@ import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml";
import {Integration} from "karavan-core/lib/model/IntegrationDefinition";
import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
import {CamelUi} from "./utils/CamelUi";
-import {BeansDesigner} from "./beans/BeansDesigner";
-import {RestDesigner} from "./rest/RestDesigner";
+import {useDesignerStore, useIntegrationStore} from "./KaravanStore";
+import {shallow} from "zustand/shallow";
import {getDesignerIcon} from "./utils/KaravanIcons";
+import {InfrastructureAPI} from "./utils/InfrastructureAPI";
+import {EventBus, IntegrationUpdate} from "./utils/EventBus";
+import {RestDesigner} from "./rest/RestDesigner";
+import {BeansDesigner} from "./beans/BeansDesigner";
interface Props {
onSave: (filename: string, yaml: string, propertyOnly: boolean) => void
@@ -36,70 +46,57 @@ interface Props {
filename: string
yaml: string
dark: boolean
+ hideLogDSL?: boolean
tab?: string
}
-interface State {
- tab: string
- integration: Integration
- key: string
- propertyOnly: boolean
-}
-
-export class KaravanInstance {
- static designer: KaravanDesigner;
-
- static set(designer: KaravanDesigner): void {
- KaravanInstance.designer = designer;
- }
-
- static get(): KaravanDesigner {
- return KaravanInstance.designer;
- }
-
- static getProps(): Props {
- return KaravanInstance.designer?.props;
- }
-}
-
-export class KaravanDesigner extends React.Component<Props, State> {
-
- getIntegration = (yaml: string, filename: string): Integration => {
- if (yaml && CamelDefinitionYaml.yamlIsIntegration(yaml)) {
- return CamelDefinitionYaml.yamlToIntegration(this.props.filename, this.props.yaml)
- } else {
- return Integration.createNew(filename, 'plain');
- }
- }
-
- public state: State = {
- tab: this.props.tab ? this.props.tab : 'routes',
- integration: this.getIntegration(this.props.yaml, this.props.filename),
- key: "",
- propertyOnly: false,
- }
-
- componentDidMount() {
- KaravanInstance.set(this);
- }
-
- componentDidUpdate = (prevProps: Readonly<Props>, prevState: Readonly<State>, snapshot?: any) => {
- if (prevState.key !== this.state.key) {
- this.props.onSave?.call(this, this.props.filename, this.getCode(this.state.integration), this.state.propertyOnly);
+export function KaravanDesigner (props: Props) {
+
+ const [tab, setTab] = useState<string>('routes');
+ const [setDark, hideLogDSL, setHideLogDSL, setSelectedStep, reset] = useDesignerStore((s) =>
+ [s.setDark, s.hideLogDSL, s.setHideLogDSL, s.setSelectedStep, s.reset], shallow)
+ const [integration, setIntegration] = useIntegrationStore((s) =>
+ [s.integration, s.setIntegration], shallow)
+
+ useEffect(() => {
+ const sub = EventBus.onIntegrationUpdate()?.subscribe((update: IntegrationUpdate) =>
+ save(update.integration, update.propertyOnly));
+ InfrastructureAPI.setOnSaveCustomCode(props.onSaveCustomCode);
+ InfrastructureAPI.setOnGetCustomCode(props.onGetCustomCode);
+ InfrastructureAPI.setOnSave(props.onSave);
+
+ setSelectedStep(undefined);
+ setIntegration(makeIntegration(props.yaml, props.filename), false);
+ reset();
+ setDark(props.dark);
+ setHideLogDSL(props.hideLogDSL === true);
+ return () => {
+ sub?.unsubscribe();
+ setSelectedStep(undefined);
+ reset();
+ };
+ }, []);
+
+ function makeIntegration(yaml: string, filename: string): Integration {
+ if (yaml && CamelDefinitionYaml.yamlIsIntegration(yaml)) {
+ return CamelDefinitionYaml.yamlToIntegration(props.filename, props.yaml)
+ } else {
+ return Integration.createNew(filename, 'plain');
}
}
- save = (integration: Integration, propertyOnly: boolean): void => {
- this.setState({key: Math.random().toString(), integration: integration, propertyOnly: propertyOnly});
+ function save(integration: Integration, propertyOnly: boolean): void {
+ const code = getCode(integration);
+ props.onSave(props.filename, code, propertyOnly);
}
- getCode = (integration: Integration): string => {
+ function getCode(integration: Integration): string {
const clone = CamelUtil.cloneIntegration(integration);
return CamelDefinitionYaml.integrationToYaml(clone);
}
- getTab(title: string, tooltip: string, icon: string) {
- const counts = CamelUi.getFlowCounts(this.state.integration);
+ function getTab(title: string, tooltip: string, icon: string) {
+ const counts = CamelUi.getFlowCounts(integration);
const count = counts.has(icon) && counts.get(icon) ? counts.get(icon) : undefined;
const showCount = count && count > 0;
return (
@@ -115,25 +112,37 @@ export class KaravanDesigner extends React.Component<Props, State> {
)
}
- render() {
- const tab = this.state.tab;
- return (
- <PageSection variant={this.props.dark ? PageSectionVariants.darker : PageSectionVariants.light} className="page" isFilled padding={{default: 'noPadding'}}>
- <Tabs className="main-tabs" activeKey={tab} onSelect={(event, tabIndex) => this.setState({tab: tabIndex.toString()})} style={{width: "100%"}}>
- <Tab eventKey='routes' title={this.getTab("Routes", "Integration flows", "routes")}></Tab>
- <Tab eventKey='rest' title={this.getTab("REST", "REST services", "rest")}></Tab>
- <Tab eventKey='beans' title={this.getTab("Beans", "Beans Configuration", "beans")}></Tab>
+ return (
+ <PageSection variant={props.dark ? PageSectionVariants.darker : PageSectionVariants.light} className="page"
+ isFilled padding={{default: 'noPadding'}}>
+ <div className={"main-tabs-wrapper"}>
+ <Tabs className="main-tabs"
+ activeKey={tab}
+ onSelect={(event, tabIndex) => {
+ setTab(tabIndex.toString());
+ setSelectedStep(undefined);
+ }}
+ style={{width: "100%"}}>
+ <Tab eventKey='routes' title={getTab("Routes", "Integration flows", "routes")}></Tab>
+ <Tab eventKey='rest' title={getTab("REST", "REST services", "rest")}></Tab>
+ <Tab eventKey='beans' title={getTab("Beans", "Beans Configuration", "beans")}></Tab>
</Tabs>
- {tab === 'routes' && <RouteDesigner integration={this.state.integration}
- onSave={(integration, propertyOnly) => this.save(integration, propertyOnly)}
- dark={this.props.dark}/>}
- {tab === 'rest' && <RestDesigner integration={this.state.integration}
- onSave={(integration, propertyOnly) => this.save(integration, propertyOnly)}
- dark={this.props.dark}/>}
- {tab === 'beans' && <BeansDesigner integration={this.state.integration}
- onSave={(integration, propertyOnly) => this.save(integration, propertyOnly)}
- dark={this.props.dark}/>}
- </PageSection>
- )
- }
+ {tab === 'routes' && <Tooltip content={"Hide Log elements"}>
+ <Switch
+ isReversed
+ isChecked={hideLogDSL}
+ onChange={(_, checked) => {
+ setHideLogDSL(checked)
+ }}
+ id="hideLogDSL"
+ name="hideLogDSL"
+ className={"hide-log"}
+ />
+ </Tooltip>}
+ </div>
+ {tab === 'routes' && <RouteDesigner/>}
+ {tab === 'rest' && <RestDesigner/>}
+ {tab === 'beans' && <BeansDesigner/>}
+ </PageSection>
+ )
}
\ No newline at end of file
diff --git a/karavan-designer/src/designer/KaravanStore.ts b/karavan-designer/src/designer/KaravanStore.ts
new file mode 100644
index 00000000..e524e625
--- /dev/null
+++ b/karavan-designer/src/designer/KaravanStore.ts
@@ -0,0 +1,244 @@
+/*
+ * 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 {CamelElement, Integration} from "karavan-core/lib/model/IntegrationDefinition";
+import {DslPosition, EventBus} from "./utils/EventBus";
+import {createWithEqualityFn} from "zustand/traditional";
+import {shallow} from "zustand/shallow";
+
+interface IntegrationState {
+ integration: Integration;
+ json: string;
+ setIntegration: (integration: Integration, propertyOnly: boolean) => void;
+ propertyOnly: boolean;
+ reset: () => void;
+}
+
+export const useIntegrationStore = createWithEqualityFn<IntegrationState>((set) => ({
+ integration: Integration.createNew("demo", "plain"),
+ propertyOnly: false,
+ json: '{}',
+ setIntegration: (integration: Integration, propertyOnly: boolean) => {
+ set((state: IntegrationState) => {
+ const json = JSON.stringify(integration);
+ if (state.json === json) {
+ return {integration: state.integration, propertyOnly: state.propertyOnly, json: state.json};
+ } else {
+ EventBus.sendIntegrationUpdate(integration, propertyOnly);
+ return {integration: integration, propertyOnly: propertyOnly, json: json};
+ }
+ })
+ },
+ reset: () => {
+ set({integration: Integration.createNew("demo", "plain"), json: '{}', propertyOnly: false});
+ }
+}), shallow)
+
+
+interface SelectorStateState {
+ showSelector: boolean;
+ setShowSelector: (showSelector: boolean) => void;
+ showSteps: boolean;
+ setShowSteps: (showSteps: boolean) => void;
+ parentDsl?: string;
+ setParentDsl: (parentDsl?: string) => void;
+ parentId: string;
+ setParentId: (parentId: string) => void;
+ selectorTabIndex?: string | number
+ setSelectorTabIndex: (selectorTabIndex?: string | number) => void;
+ selectedPosition?: number;
+ setSelectedPosition: (selectedPosition?: number) => void;
+ selectedLabels: string [];
+ addSelectedLabel: (label: string) => void;
+ deleteSelectedLabel: (label: string) => void;
+ clearSelectedLabels: () => void;
+}
+
+export const useSelectorStore = createWithEqualityFn<SelectorStateState>((set) => ({
+ showSelector: false,
+ deleteMessage: '',
+ parentId: '',
+ showSteps: true,
+ selectedLabels: [],
+ addSelectedLabel: (label: string) => {
+ set(state => ({
+ selectedLabels: [...state.selectedLabels, label]
+ }))
+ },
+ deleteSelectedLabel: (label: string) => {
+ set(state => ({
+ selectedLabels: [...state.selectedLabels.filter(x => x !== label)]
+ }))
+ },
+ clearSelectedLabels: () => {
+ set((state: SelectorStateState) => {
+ state.selectedLabels.length = 0;
+ return state;
+ })
+ },
+ setSelectedLabels: (selectedLabels: string []) => {
+ set({selectedLabels: selectedLabels})
+ },
+ setSelectorTabIndex: (selectorTabIndex?: string | number) => {
+ set({selectorTabIndex: selectorTabIndex})
+ },
+ setParentDsl: (parentDsl?: string) => {
+ set({parentDsl: parentDsl})
+ },
+ setShowSelector: (showSelector: boolean) => {
+ set({showSelector: showSelector})
+ },
+ setShowSteps: (showSteps: boolean) => {
+ set({showSteps: showSteps})
+ },
+ setParentId: (parentId: string) => {
+ set({parentId: parentId})
+ },
+ setSelectedPosition: (selectedPosition?: number) => {
+ set({selectedPosition: selectedPosition})
+ },
+}), shallow)
+
+
+interface ConnectionsState {
+ steps: Map<string, DslPosition>;
+ addStep: (uuid: string, position: DslPosition) => void;
+ deleteStep: (uuid: string) => void;
+ clearSteps: () => void;
+ setSteps: (steps: Map<string, DslPosition>) => void;
+}
+
+export const useConnectionsStore = createWithEqualityFn<ConnectionsState>((set) => ({
+ steps: new Map<string, DslPosition>(),
+ addStep: (uuid: string, position: DslPosition) => {
+ set(state => ({
+ steps: new Map(state.steps).set(uuid, position),
+ }))
+ },
+ deleteStep: (uuid: string) => {
+ set((state: ConnectionsState) => {
+ // state.steps.clear();
+ Array.from(state.steps.entries())
+ .filter(value => value[1]?.parent?.uuid !== uuid)
+ .forEach(value => state.steps.set(value[0], value[1]));
+ state.steps.delete(uuid)
+ return state;
+ })
+ },
+ clearSteps: () => {
+ set((state: ConnectionsState) => {
+ state.steps.clear();
+ return state;
+ })
+ },
+ setSteps: (steps: Map<string, DslPosition>) => {
+ set({steps: steps})
+ },
+}), shallow)
+
+type DesignerState = {
+ dark: boolean;
+ hideLogDSL: boolean;
+ shiftKeyPressed: boolean;
+ showDeleteConfirmation: boolean;
+ showMoveConfirmation: boolean;
+ deleteMessage: string;
+ selectedStep?: CamelElement;
+ selectedUuids: string[];
+ clipboardSteps: CamelElement[];
+ width: number,
+ height: number,
+ top: number,
+ left: number,
+}
+const designerState: DesignerState = {
+ dark: false,
+ hideLogDSL: false,
+ shiftKeyPressed: false,
+ showDeleteConfirmation: false,
+ showMoveConfirmation: false,
+ deleteMessage: '',
+ selectedUuids: [],
+ clipboardSteps: [],
+ width: 0,
+ height: 0,
+ top: 0,
+ left: 0,
+};
+
+type DesignerAction = {
+ setDark: (dark: boolean) => void;
+ setHideLogDSL: (hideLogDSL: boolean) => void;
+ setShiftKeyPressed: (shiftKeyPressed: boolean) => void;
+ setShowDeleteConfirmation: (showDeleteConfirmation: boolean) => void;
+ setShowMoveConfirmation: (showMoveConfirmation: boolean) => void;
+ setDeleteMessage: (deleteMessage: string) => void;
+ setSelectedStep: (selectedStep?: CamelElement) => void;
+ setSelectedUuids: (selectedUuids: string[]) => void;
+ setClipboardSteps: (clipboardSteps: CamelElement[]) => void;
+ setPosition: (width: number, height: number, top: number, left: number) => void;
+ reset: () => void;
+}
+
+export const useDesignerStore = createWithEqualityFn<DesignerState & DesignerAction>((set) => ({
+ ...designerState,
+ setDark: (dark: boolean) => {
+ set({dark: dark})
+ },
+ setHideLogDSL: (hideLogDSL: boolean) => {
+ set({hideLogDSL: hideLogDSL})
+ },
+ setShiftKeyPressed: (shiftKeyPressed: boolean) => {
+ set({shiftKeyPressed: shiftKeyPressed})
+ },
+ setSelectedStep: (selectedStep?: CamelElement) => {
+ set({selectedStep: selectedStep})
+ },
+ setShowDeleteConfirmation: (showDeleteConfirmation: boolean) => {
+ set({showDeleteConfirmation: showDeleteConfirmation})
+ },
+ setShowMoveConfirmation: (showMoveConfirmation: boolean) => {
+ set({showMoveConfirmation: showMoveConfirmation})
+ },
+ setDeleteMessage: (deleteMessage: string) => {
+ set({deleteMessage: deleteMessage})
+ },
+ setSelectedUuids: (selectedUuids: string[]) => {
+ set((state: DesignerState) => {
+ state.selectedUuids.length = 0;
+ state.selectedUuids.push(...selectedUuids);
+ return state;
+ })
+ },
+ setClipboardSteps: (clipboardSteps: CamelElement[]) => {
+ set((state: DesignerState) => {
+ state.clipboardSteps.length = 0;
+ state.clipboardSteps.push(...clipboardSteps);
+ return state;
+ })
+ },
+ width: 100,
+ height: 100,
+ top: 0,
+ left: 0,
+ setPosition: (width: number, height: number, top: number, left: number) => {
+ set({width: width, height: height, top: top, left: left})
+ },
+ reset: () => {
+ set(designerState);
+ }
+}), shallow)
\ No newline at end of file
diff --git a/karavan-designer/src/designer/MainToolbar.tsx b/karavan-designer/src/designer/MainToolbar.tsx
index ed344840..8923b70c 100644
--- a/karavan-designer/src/designer/MainToolbar.tsx
+++ b/karavan-designer/src/designer/MainToolbar.tsx
@@ -9,7 +9,7 @@ interface Props {
tools: React.ReactNode;
}
-export const MainToolbar = (props: Props) => {
+export function MainToolbar(props: Props) {
return (
<PageSection className="tools-section" variant={PageSectionVariants.light}>
diff --git a/karavan-designer/src/designer/beans/BeanCard.tsx b/karavan-designer/src/designer/beans/BeanCard.tsx
index d9eac357..74dd884b 100644
--- a/karavan-designer/src/designer/beans/BeanCard.tsx
+++ b/karavan-designer/src/designer/beans/BeanCard.tsx
@@ -19,47 +19,42 @@ import {
Button
} from '@patternfly/react-core';
import '../karavan.css';
-import {Integration} from "karavan-core/lib/model/IntegrationDefinition";
import {RegistryBeanDefinition} from "karavan-core/lib/model/CamelDefinition";
import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-circle-icon";
+import {useDesignerStore} from "../KaravanStore";
+import {shallow} from "zustand/shallow";
interface Props {
bean: RegistryBeanDefinition
- selectedStep?: RegistryBeanDefinition
- integration: Integration
selectElement: (element: RegistryBeanDefinition) => void
deleteElement: (element: RegistryBeanDefinition) => void
}
-export class BeanCard extends React.Component<Props, any> {
+export function BeanCard (props: Props) {
- selectElement = (evt: React.MouseEvent) => {
+ const [ selectedStep] = useDesignerStore((s) => [s.selectedStep], shallow)
+
+ function selectElement (evt: React.MouseEvent) {
evt.stopPropagation();
- this.props.selectElement.call(this, this.props.bean);
+ props.selectElement(props.bean);
}
- delete = (evt: React.MouseEvent) => {
+ function onDelete (evt: React.MouseEvent) {
evt.stopPropagation();
- this.props.deleteElement.call(this, this.props.bean);
+ props.deleteElement(props.bean);
}
- render() {
- const bean = this.props.bean;
- return (
- <div className={this.props.selectedStep?.uuid === bean.uuid ? "rest-card rest-card-selected" : "rest-card rest-card-unselected"} onClick={e => this.selectElement(e)}>
- <div className="header">
- <div className="title">BEAN</div>
- <div className="title">{bean.name}</div>
- <div className="description">{bean.type}</div>
- {/*<Tooltip position={"bottom"} content={<div>Add REST method</div>}>*/}
- {/*<Button variant={"link"} icon={<AddIcon/>} aria-label="Add" onClick={e => this.selectMethod(e)} className="add-button">Add method</Button>*/}
- {/*</Tooltip>*/}
- <Button variant="link" className="delete-button" onClick={e => this.delete(e)}><DeleteIcon/></Button>
- </div>
- <div className="rest-content" key={Math.random().toString()}>
-
- </div>
+ const bean = props.bean;
+ return (
+ <div className={selectedStep?.uuid === bean.uuid ? "rest-card rest-card-selected" : "rest-card rest-card-unselected"} onClick={e => selectElement(e)}>
+ <div className="header">
+ <div className="title">Bean</div>
+ <div className="title">{bean.name}</div>
+ <div className="description">{bean.type}</div>
+ <Button variant="link" className="delete-button" onClick={e => onDelete(e)}>
+ <DeleteIcon/>
+ </Button>
</div>
- );
- }
+ </div>
+ )
}
diff --git a/karavan-designer/src/designer/beans/BeanProperties.tsx b/karavan-designer/src/designer/beans/BeanProperties.tsx
index 4470c01a..b271ce8d 100644
--- a/karavan-designer/src/designer/beans/BeanProperties.tsx
+++ b/karavan-designer/src/designer/beans/BeanProperties.tsx
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import React from 'react';
+import React, {useState} from 'react';
import {
Form,
FormGroup,
@@ -31,7 +31,6 @@ import {SensitiveKeys} from "karavan-core/lib/model/CamelMetadata";
import {v4 as uuidv4} from "uuid";
import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon";
import AddIcon from "@patternfly/react-icons/dist/js/icons/plus-circle-icon";
-import {IntegrationHeader} from "../utils/KaravanComponents";
import CloneIcon from '@patternfly/react-icons/dist/esm/icons/clone-icon'
import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon";
import {InfrastructureSelector} from "../route/property/InfrastructureSelector";
@@ -40,6 +39,9 @@ import {InfrastructureAPI} from "../utils/InfrastructureAPI";
import ShowIcon from "@patternfly/react-icons/dist/js/icons/eye-icon";
import HideIcon from "@patternfly/react-icons/dist/js/icons/eye-slash-icon";
import DockerIcon from "@patternfly/react-icons/dist/js/icons/docker-icon";
+import {useDesignerStore} from "../KaravanStore";
+import {shallow} from "zustand/shallow";
+import {IntegrationHeader} from "../utils/IntegrationHeader";
interface Props {
@@ -50,110 +52,95 @@ interface Props {
onClone: (bean: RegistryBeanDefinition) => void
}
-interface State {
- bean?: RegistryBeanDefinition
- properties: Map<string, [string, string, boolean]>
- key: string,
- showInfrastructureSelector: boolean
- infrastructureSelectorUuid?: string
- infrastructureSelectorProperty?: string
-}
+export function BeanProperties (props: Props) {
-export class BeanProperties extends React.Component<Props, State> {
+ const [selectedStep] = useDesignerStore((s) => [s.selectedStep], shallow);
+ const [infrastructureSelector, setInfrastructureSelector] = useState<boolean>(false);
+ const [infrastructureSelectorProperty, setInfrastructureSelectorProperty] = useState<string | undefined>(undefined);
+ const [infrastructureSelectorUuid, setInfrastructureSelectorUuid] = useState<string | undefined>(undefined);
+ const [properties, setProperties] =
+ useState<Map<string, [string, string, boolean]>>(props.bean?.properties ? preparePropertiesMap(props.bean?.properties) : new Map<string, [string, string, boolean]>());
- preparePropertiesMap = (properties: any): Map<string, [string, string, boolean]> => {
+
+ function preparePropertiesMap (properties: any): Map<string, [string, string, boolean]> {
const result = new Map<string, [string, string, boolean]>();
Object.keys(properties).forEach((k, i, a) => result.set(uuidv4(), [k, properties[k], false]));
return result;
}
- public state: State = {
- bean: this.props.bean,
- key: '',
- showInfrastructureSelector: false,
- properties: this.props.bean?.properties ? this.preparePropertiesMap(this.props.bean?.properties) : new Map<string, [string, string, boolean]>()
- };
-
- componentDidUpdate = (prevProps: Readonly<Props>, prevState: Readonly<State>, snapshot?: any) => {
- if (prevProps.bean?.uuid !== this.props.bean?.uuid) {
- this.setBean(this.props.bean);
- }
- if (prevState.key !== this.state.key && this.state.bean) {
- const bean = CamelUtil.cloneBean(this.state.bean);
- const properties: any = {};
- this.state.properties.forEach(p => properties[p[0]] = p[1]);
- bean.properties = properties;
- this.setState({bean: bean});
- this.props.onChange?.call(this, bean);
+ function onBeanPropertyUpdate () {
+ if (selectedStep) {
+ const bean = CamelUtil.cloneBean(selectedStep);
+ const beanProperties: any = {};
+ properties.forEach((p: any) => beanProperties[p[0]] = p[1]);
+ bean.properties = beanProperties;
+ props.onChange(bean);
}
}
- setBean = (bean?: RegistryBeanDefinition) => {
- this.setState({
- bean: bean,
- properties: bean?.properties ? this.preparePropertiesMap(bean.properties) : new Map<string, [string, string, false]>()
- });
- }
-
- beanChanged = (fieldId: string, value: string) => {
- if (this.state.bean) {
- const bean = CamelUtil.cloneBean(this.state.bean);
+ function beanFieldChanged (fieldId: string, value: string) {
+ if (selectedStep) {
+ const bean = CamelUtil.cloneBean(selectedStep);
(bean as any)[fieldId] = value;
- this.setState({bean: bean});
- this.props.onChange?.call(this, bean);
+ props.onChange(bean);
}
}
- propertyChanged = (uuid: string, key: string, value: string, showPassword: boolean) => {
- this.setState(state => {
- state.properties.set(uuid, [key, value, showPassword]);
- return {properties: state.properties, key: Math.random().toString()};
- })
+ function propertyChanged (uuid: string, key: string, value: string, showPassword: boolean) {
+ setProperties(prevState => {
+ prevState.set(uuid, [key, value, showPassword]);
+ return prevState;
+ });
+ onBeanPropertyUpdate();
}
- propertyDeleted = (uuid: string) => {
- this.setState(state => {
- state.properties.delete(uuid);
- return {properties: state.properties, key: Math.random().toString()};
+ function propertyDeleted (uuid: string) {
+ setProperties(prevState => {
+ prevState.delete(uuid);
+ return prevState;
})
+ onBeanPropertyUpdate();
}
- selectInfrastructure = (value: string) => {
- const propertyId = this.state.infrastructureSelectorProperty;
- const uuid = this.state.infrastructureSelectorUuid;
+ function selectInfrastructure (value: string) {
+ const propertyId = infrastructureSelectorProperty;
+ const uuid = infrastructureSelectorUuid;
if (propertyId && uuid){
if (value.startsWith("config") || value.startsWith("secret")) value = "{{" + value + "}}";
- this.propertyChanged(uuid, propertyId, value, false);
- this.setState({showInfrastructureSelector: false, infrastructureSelectorProperty: undefined})
+ propertyChanged(uuid, propertyId, value, false);
+ setInfrastructureSelector(false);
+ setInfrastructureSelectorProperty(undefined);
}
}
- openInfrastructureSelector = (uuid: string, propertyName: string) => {
- this.setState({infrastructureSelectorUuid: uuid, infrastructureSelectorProperty: propertyName, showInfrastructureSelector: true});
+ function openInfrastructureSelector (uuid: string, propertyName: string) {
+ setInfrastructureSelector(true);
+ setInfrastructureSelectorProperty(propertyName);
+ setInfrastructureSelectorUuid(uuid);
}
- closeInfrastructureSelector = () => {
- this.setState({showInfrastructureSelector: false})
+ function closeInfrastructureSelector () {
+ setInfrastructureSelector(false);
}
- getInfrastructureSelectorModal() {
+ function getInfrastructureSelectorModal() {
return (
<InfrastructureSelector
dark={false}
- isOpen={this.state.showInfrastructureSelector}
- onClose={() => this.closeInfrastructureSelector()}
- onSelect={this.selectInfrastructure}/>)
+ isOpen={infrastructureSelector}
+ onClose={() => closeInfrastructureSelector()}
+ onSelect={selectInfrastructure}/>)
}
- cloneBean = () => {
- if (this.state.bean) {
- const bean = CamelUtil.cloneBean(this.state.bean);
+ function cloneBean () {
+ if (selectedStep) {
+ const bean = CamelUtil.cloneBean(selectedStep);
bean.uuid = uuidv4();
- this.props.onClone?.call(this, bean);
+ props.onClone(bean);
}
}
- getLabelIcon = (displayName: string, description: string) => {
+ function getLabelIcon (displayName: string, description: string) {
return (
<Popover
position={"left"}
@@ -174,28 +161,28 @@ export class BeanProperties extends React.Component<Props, State> {
)
}
- getBeanForm() {
- const bean = this.state.bean;
+ function getBeanForm() {
+ const bean = (selectedStep as RegistryBeanDefinition);
return (
<>
<div className="headers">
<div className="top">
<Title headingLevel="h1" size="md">Bean</Title>
<Tooltip content="Clone bean" position="bottom">
- <Button variant="link" onClick={() => this.cloneBean()} icon={<CloneIcon/>}/>
+ <Button variant="link" onClick={() => cloneBean()} icon={<CloneIcon/>}/>
</Tooltip>
</div>
</div>
- <FormGroup label="Name" fieldId="name" isRequired labelIcon={this.getLabelIcon("Name", "Bean name used as a reference ex: myBean")}>
+ <FormGroup label="Name" fieldId="name" isRequired labelIcon={getLabelIcon("Name", "Bean name used as a reference ex: myBean")}>
<TextInput className="text-field" isRequired type="text" id="name" name="name" value={bean?.name}
- onChange={(_, value)=> this.beanChanged("name", value)}/>
+ onChange={(_, value)=> beanFieldChanged("name", value)}/>
</FormGroup>
- <FormGroup label="Type" fieldId="type" isRequired labelIcon={this.getLabelIcon("Type", "Bean class Canonical Name ex: org.demo.MyBean")}>
+ <FormGroup label="Type" fieldId="type" isRequired labelIcon={getLabelIcon("Type", "Bean class Canonical Name ex: org.demo.MyBean")}>
<TextInput className="text-field" isRequired type="text" id="type" name="type" value={bean?.type}
- onChange={(_, value) => this.beanChanged("type", value)}/>
+ onChange={(_, value) => beanFieldChanged("type", value)}/>
</FormGroup>
<FormGroup label="Properties" fieldId="properties" className="bean-properties">
- {Array.from(this.state.properties.entries()).map((v, index, array) => {
+ {Array.from(properties.entries()).map((v, index, array) => {
const i = v[0];
const key = v[1][0];
const value = v[1][1];
@@ -207,12 +194,12 @@ export class BeanProperties extends React.Component<Props, State> {
<div key={"key-" + i} className="bean-property">
<TextInput placeholder="Bean Field Name" className="text-field" isRequired type="text" id="key" name="key" value={key}
onChange={(_, beanFieldName) => {
- this.propertyChanged(i, beanFieldName, value, showPassword)
+ propertyChanged(i, beanFieldName, value, showPassword)
}}/>
<InputGroup>
{inInfrastructure &&
<Tooltip position="bottom-end" content="Select value from Infrastructure">
- <Button variant="control" onClick={e => this.openInfrastructureSelector(i, key)}>
+ <Button variant="control" onClick={e => openInfrastructureSelector(i, key)}>
{icon}
</Button>
</Tooltip>}
@@ -226,35 +213,34 @@ export class BeanProperties extends React.Component<Props, State> {
name="value"
value={value}
onChange={(_, value) => {
- this.propertyChanged(i, key, value, showPassword)
+ propertyChanged(i, key, value, showPassword)
}}/>
</InputGroupItem>
{isSecret && <Tooltip position="bottom-end" content={showPassword ? "Hide" : "Show"}>
- <Button variant="control" onClick={e => this.propertyChanged(i, key, value, !showPassword)}>
+ <Button variant="control" onClick={e => propertyChanged(i, key, value, !showPassword)}>
{showPassword ? <ShowIcon/> : <HideIcon/>}
</Button>
</Tooltip>}
</InputGroup>
- <Button variant="link" className="delete-button" onClick={e => this.propertyDeleted(i)}><DeleteIcon/></Button>
+ <Button variant="link" className="delete-button" onClick={e => propertyDeleted(i)}><DeleteIcon/></Button>
</div>
)
})}
- <Button variant="link" className="add-button" onClick={e => this.propertyChanged(uuidv4(), '', '', false)}>
+ <Button variant="link" className="add-button" onClick={e => propertyChanged(uuidv4(), '', '', false)}>
<AddIcon/>Add property</Button>
</FormGroup>
</>
)
}
- render() {
- return (
- <div className='properties' key={this.state.bean ? this.state.bean.uuid : 'integration'}>
- <Form autoComplete="off" onSubmit={event => event.preventDefault()}>
- {this.state.bean === undefined && <IntegrationHeader integration={this.props.integration}/>}
- {this.state.bean !== undefined && this.getBeanForm()}
- </Form>
- {this.getInfrastructureSelectorModal()}
- </div>
- )
- }
+ const bean = (selectedStep as RegistryBeanDefinition);
+ return (
+ <div className='properties' key={bean ? bean.uuid : 'integration'}>
+ <Form autoComplete="off" onSubmit={event => event.preventDefault()}>
+ {bean === undefined && <IntegrationHeader/>}
+ {bean !== undefined && getBeanForm()}
+ </Form>
+ {getInfrastructureSelectorModal()}
+ </div>
+ )
}
diff --git a/karavan-designer/src/designer/beans/BeansDesigner.tsx b/karavan-designer/src/designer/beans/BeansDesigner.tsx
index 2c9a681f..ae603aac 100644
--- a/karavan-designer/src/designer/beans/BeansDesigner.tsx
+++ b/karavan-designer/src/designer/beans/BeansDesigner.tsx
@@ -20,145 +20,114 @@ import {
} from '@patternfly/react-core';
import '../karavan.css';
import {RegistryBeanDefinition} from "karavan-core/lib/model/CamelDefinition";
-import {Integration} from "karavan-core/lib/model/IntegrationDefinition";
import {CamelUi} from "../utils/CamelUi";
import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon";
import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
import {BeanProperties} from "./BeanProperties";
import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
import {BeanCard} from "./BeanCard";
+import {useDesignerStore, useIntegrationStore} from "../KaravanStore";
+import {shallow} from "zustand/shallow";
-interface Props {
- onSave?: (integration: Integration, propertyOnly: boolean) => void
- integration: Integration
- dark: boolean
-}
-
-interface State {
- integration: Integration
- showDeleteConfirmation: boolean
- selectedBean?: RegistryBeanDefinition
- key: string
- propertyOnly: boolean
-}
+export function BeansDesigner () {
-export class BeansDesigner extends React.Component<Props, State> {
+ const [integration, setIntegration] = useIntegrationStore((s) => [s.integration, s.setIntegration], shallow)
+ const [dark, selectedStep, showDeleteConfirmation, setShowDeleteConfirmation, setSelectedStep] = useDesignerStore((s) =>
+ [s.dark, s.selectedStep, s.showDeleteConfirmation, s.setShowDeleteConfirmation, s.setSelectedStep], shallow)
- public state: State = {
- integration: this.props.integration,
- showDeleteConfirmation: false,
- key: "",
- propertyOnly: false
- };
-
- componentDidUpdate = (prevProps: Readonly<Props>, prevState: Readonly<State>, snapshot?: any) => {
- if (prevState.key !== this.state.key) {
- this.props.onSave?.call(this, this.state.integration, this.state.propertyOnly);
- }
- }
- showDeleteConfirmation = (bean: RegistryBeanDefinition) => {
- this.setState({selectedBean: bean, showDeleteConfirmation: true});
+ function onShowDeleteConfirmation (bean: RegistryBeanDefinition) {
+ setSelectedStep(bean);
+ setShowDeleteConfirmation(true);
}
- onIntegrationUpdate = (i: Integration) => {
- this.setState({integration: i, propertyOnly: false, showDeleteConfirmation: false, key: Math.random().toString()});
+ function deleteBean () {
+ const i = CamelDefinitionApiExt.deleteBeanFromIntegration(integration, selectedStep);
+ setIntegration(i, false);
+ setShowDeleteConfirmation(false);
+ setSelectedStep(undefined);
}
- deleteBean = () => {
- const i = CamelDefinitionApiExt.deleteBeanFromIntegration(this.state.integration, this.state.selectedBean);
- this.setState({
- integration: i,
- showDeleteConfirmation: false,
- key: Math.random().toString(),
- selectedBean: new RegistryBeanDefinition(),
- propertyOnly: false
- });
- }
-
- changeBean = (bean: RegistryBeanDefinition) => {
- const clone = CamelUtil.cloneIntegration(this.state.integration);
+ function changeBean (bean: RegistryBeanDefinition) {
+ const clone = CamelUtil.cloneIntegration(integration);
const i = CamelDefinitionApiExt.addBeanToIntegration(clone, bean);
- this.setState({integration: i, propertyOnly: false, key: Math.random().toString(), selectedBean: bean});
+ setIntegration(i, false);
+ setSelectedStep(bean);
}
- getDeleteConfirmation() {
+ function getDeleteConfirmation() {
return (<Modal
className="modal-delete"
title="Confirmation"
- isOpen={this.state.showDeleteConfirmation}
- onClose={() => this.setState({showDeleteConfirmation: false})}
+ isOpen={showDeleteConfirmation}
+ onClose={() => setShowDeleteConfirmation(false)}
actions={[
- <Button key="confirm" variant="primary" onClick={e => this.deleteBean()}>Delete</Button>,
+ <Button key="confirm" variant="primary" onClick={e => deleteBean()}>Delete</Button>,
<Button key="cancel" variant="link"
- onClick={e => this.setState({showDeleteConfirmation: false})}>Cancel</Button>
+ onClick={e => setShowDeleteConfirmation(false)}>Cancel</Button>
]}
- onEscapePress={e => this.setState({showDeleteConfirmation: false})}>
+ onEscapePress={e => setShowDeleteConfirmation(false)}>
<div>
Delete bean from integration?
</div>
</Modal>)
}
- selectBean = (bean?: RegistryBeanDefinition) => {
- this.setState({selectedBean: bean})
+ function selectBean (bean?: RegistryBeanDefinition) {
+ setSelectedStep(bean);
}
- unselectBean = (evt: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
+ function unselectBean (evt: React.MouseEvent<HTMLDivElement, MouseEvent>) {
if ((evt.target as any).dataset.click === 'BEANS') {
evt.stopPropagation()
- this.setState({selectedBean: undefined})
+ setSelectedStep(undefined);
}
};
- createBean = () => {
- this.changeBean(new RegistryBeanDefinition());
+ function createBean () {
+ changeBean(new RegistryBeanDefinition());
}
- getPropertiesPanel() {
+ function getPropertiesPanel() {
return (
<DrawerPanelContent isResizable hasNoBorder defaultSize={'400px'} maxSize={'800px'} minSize={'300px'}>
- <BeanProperties integration={this.props.integration}
- bean={this.state.selectedBean}
- dark={this.props.dark}
- onChange={this.changeBean}
- onClone={this.changeBean}/>
+ <BeanProperties integration={integration}
+ bean={selectedStep}
+ dark={dark}
+ onChange={changeBean}
+ onClone={changeBean}/>
</DrawerPanelContent>
)
}
- render() {
- const beans = CamelUi.getBeans(this.state.integration);
- return (
- <PageSection className="rest-page" isFilled padding={{default: 'noPadding'}}>
- <div className="rest-page-columns">
- <Drawer isExpanded isInline>
- <DrawerContent panelContent={this.getPropertiesPanel()}>
- <DrawerContentBody>
- <div className="graph" data-click="REST" onClick={event => this.unselectBean(event)}>
- <div className="flows">
- {beans?.map(bean => <BeanCard key={bean.uuid + this.state.key}
- selectedStep={this.state.selectedBean}
- bean={bean}
- integration={this.props.integration}
- selectElement={this.selectBean}
- deleteElement={this.showDeleteConfirmation}/>)}
- <div className="add-rest">
- <Button
- variant={beans?.length === 0 ? "primary" : "secondary"}
- data-click="ADD_REST"
- icon={<PlusIcon/>}
- onClick={e => this.createBean()}>Create bean
- </Button>
- </div>
+ const beans = CamelUi.getBeans(integration);
+ return (
+ <PageSection className="rest-page" isFilled padding={{default: 'noPadding'}}>
+ <div className="rest-page-columns">
+ <Drawer isExpanded isInline>
+ <DrawerContent panelContent={getPropertiesPanel()}>
+ <DrawerContentBody>
+ <div className="graph" data-click="REST" onClick={event => unselectBean(event)}>
+ <div className="flows">
+ {beans?.map((bean, index) => <BeanCard key={bean.uuid + index}
+ bean={bean}
+ selectElement={selectBean}
+ deleteElement={onShowDeleteConfirmation}/>)}
+ <div className="add-rest">
+ <Button
+ variant={beans?.length === 0 ? "primary" : "secondary"}
+ data-click="ADD_REST"
+ icon={<PlusIcon/>}
+ onClick={e => createBean()}>Create bean
+ </Button>
</div>
</div>
- </DrawerContentBody>
- </DrawerContent>
- </Drawer>
- </div>
- {this.getDeleteConfirmation()}
- </PageSection>
- )
- }
+ </div>
+ </DrawerContentBody>
+ </DrawerContent>
+ </Drawer>
+ </div>
+ {getDeleteConfirmation()}
+ </PageSection>
+ )
}
diff --git a/karavan-designer/src/designer/karavan.css b/karavan-designer/src/designer/karavan.css
index 049e4e7a..72597f22 100644
--- a/karavan-designer/src/designer/karavan.css
+++ b/karavan-designer/src/designer/karavan.css
@@ -34,6 +34,11 @@
height: 36px;
}
+.karavan .pf-v5-c-switch__input:focus ~ .pf-v5-c-switch__toggle {
+ outline: transparent;
+ outline-offset: 0;
+}
+
.karavan .header-button {
margin-left: var(--pf-v5-c-page__header-tools--MarginRight);
}
@@ -231,10 +236,13 @@
flex-direction: column;
}
+.karavan main {
+ overflow: hidden;
+}
+
/*DSL*/
.karavan .dsl-page {
- flex: 1;
- overflow: auto;
+ height: 100%;
}
.karavan .dsl-page .dsl-page-columns {
@@ -247,12 +255,31 @@
height: 24px;
}
-.karavan .main-tabs .top-menu-item {
+.karavan .designer-page .main-tabs-wrapper {
display: flex;
flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ position: relative;
}
-.karavan .main-tabs .top-menu-item .count {
+.karavan .designer-page .main-tabs-wrapper::before {
+ content: "";
+ position: absolute;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ border: solid;
+ border-width: 0 0 var(--pf-v5-global--BorderWidth--sm) 0;
+ border-bottom-color: var(--pf-v5-global--BorderColor--100);
+}
+
+.karavan .designer-page .main-tabs-wrapper .main-tabs .top-menu-item {
+ display: flex;
+ flex-direction: row;
+}
+
+.karavan .designer-page .main-tabs-wrapper .main-tabs .top-menu-item .count {
background: var(--pf-v5-global--active-color--100);
color: white;
height: fit-content;
@@ -261,12 +288,12 @@
min-width: 0px;
}
-.karavan .main-tabs .pf-v5-c-tabs__link .pf-v5-c-tabs__item-icon {
+.karavan .designer-page .main-tabs-wrapper .main-tabs .pf-v5-c-tabs__link .pf-v5-c-tabs__item-icon {
height: 24px;
margin-right: 0;
}
-.karavan .main-tabs .pf-v5-c-tabs__item-text {
+.karavan .designer-page .main-tabs-wrapper .main-tabs .pf-v5-c-tabs__item-text {
font-size: 14px;
font-weight: bold;
margin-top: auto;
@@ -274,6 +301,11 @@
margin-right: 6px;
}
+.karavan .designer-page .page .main-tabs-wrapper .hide-log {
+ white-space: nowrap;
+ margin-right: 16px;
+}
+
/*Properties*/
.karavan .properties {
border: 1px solid #eee;
@@ -551,8 +583,8 @@
.karavan .dsl-page .flows {
width: 100%;
- position: relative;
- margin-bottom: 80px;
+ position: absolute;
+ /*margin-bottom: 80px;*/
}
.karavan .dsl-page .flows .add-flow {
@@ -1384,6 +1416,9 @@
.karavan .designer-page {
background-color: white;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
}
.karavan .designer-page .project-page-section {
diff --git a/karavan-designer/src/designer/rest/RestCard.tsx b/karavan-designer/src/designer/rest/RestCard.tsx
index e4e487f7..5177f41c 100644
--- a/karavan-designer/src/designer/rest/RestCard.tsx
+++ b/karavan-designer/src/designer/rest/RestCard.tsx
@@ -20,7 +20,7 @@ import {
} from '@patternfly/react-core';
import '../karavan.css';
import {CamelElement, Integration} from "karavan-core/lib/model/IntegrationDefinition";
-import {RestDefinition} from "karavan-core/lib/model/CamelDefinition";
+import {GetDefinition, RestDefinition} from "karavan-core/lib/model/CamelDefinition";
import {RestMethodCard} from "./RestMethodCard";
import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-circle-icon";
import AddIcon from "@patternfly/react-icons/dist/js/icons/plus-circle-icon";
@@ -34,45 +34,76 @@ interface Props {
deleteElement: (element: CamelElement) => void
}
-export class RestCard extends React.Component<Props, any> {
+export function RestCard(props: Props) {
- selectElement = (evt: React.MouseEvent) => {
+ function selectElement(evt: React.MouseEvent) {
evt.stopPropagation();
- this.props.selectElement.call(this, this.props.rest);
+ props.selectElement(props.rest);
}
- selectMethod = (evt: React.MouseEvent) => {
+ function selectMethod(evt: React.MouseEvent) {
evt.stopPropagation();
- this.props.selectMethod.call(this, this.props.rest);
+ props.selectMethod(props.rest);
}
- delete = (evt: React.MouseEvent) => {
+ function onDelete(evt: React.MouseEvent) {
evt.stopPropagation();
- this.props.deleteElement.call(this, this.props.rest);
+ props.deleteElement(props.rest);
}
- render() {
- const rest = this.props.rest;
- return (
- <div className={this.props.selectedStep?.uuid === rest.uuid ? "rest-card rest-card-selected" : "rest-card rest-card-unselected"} onClick={e => this.selectElement(e)}>
- <div className="header">
- <div className="title">REST</div>
- <div className="title">{rest.path}</div>
- <div className="description">{rest.description}</div>
- <Tooltip position={"bottom"} content={<div>Add REST method</div>}>
- <Button variant={"link"} icon={<AddIcon/>} aria-label="Add" onClick={e => this.selectMethod(e)} className="add-button">Add method</Button>
- </Tooltip>
- <Button variant="link" className="delete-button" onClick={e => this.delete(e)}><DeleteIcon/></Button>
- </div>
- <div className="rest-content" key={Math.random().toString()}>
- {rest.get?.map(get => <RestMethodCard key={get.uuid} method={get} selectedStep={this.props.selectedStep} integration={this.props.integration} selectElement={this.props.selectElement} deleteElement={this.props.deleteElement}/>)}
- {rest.post?.map(post => <RestMethodCard key={post.uuid} method={post} selectedStep={this.props.selectedStep} integration={this.props.integration} selectElement={this.props.selectElement} deleteElement={this.props.deleteElement}/>)}
- {rest.put?.map(put => <RestMethodCard key={put.uuid} method={put} selectedStep={this.props.selectedStep} integration={this.props.integration} selectElement={this.props.selectElement} deleteElement={this.props.deleteElement}/>)}
- {rest.patch?.map(patch => <RestMethodCard key={patch.uuid} method={patch} selectedStep={this.props.selectedStep} integration={this.props.integration} selectElement={this.props.selectElement} deleteElement={this.props.deleteElement}/>)}
- {rest.delete?.map(del => <RestMethodCard key={del.uuid} method={del} selectedStep={this.props.selectedStep} integration={this.props.integration} selectElement={this.props.selectElement} deleteElement={this.props.deleteElement}/>)}
- {rest.head?.map(head => <RestMethodCard key={head.uuid} method={head} selectedStep={this.props.selectedStep} integration={this.props.integration} selectElement={this.props.selectElement} deleteElement={this.props.deleteElement}/>)}
- </div>
+ const rest = props.rest;
+ return (
+ <div
+ className={props.selectedStep?.uuid === rest.uuid ? "rest-card rest-card-selected" : "rest-card rest-card-unselected"}
+ onClick={e => selectElement(e)}>
+ <div className="header">
+ <div className="title">REST</div>
+ <div className="title">{rest.path}</div>
+ <div className="description">{rest.description}</div>
+ <Tooltip position={"bottom"} content={<div>Add REST method</div>}>
+ <Button variant={"link"} icon={<AddIcon/>} aria-label="Add" onClick={e => selectMethod(e)}
+ className="add-button">Add method</Button>
+ </Tooltip>
+ <Button variant="link" className="delete-button" onClick={e => onDelete(e)}><DeleteIcon/></Button>
</div>
- );
- }
+ <div className="rest-content" key={Math.random().toString()}>
+ {rest.get?.map((get: GetDefinition) =>
+ <RestMethodCard key={get.uuid}
+ method={get}
+ selectElement={props.selectElement}
+ deleteElement={props.deleteElement}
+ />)}
+ {rest.post?.map(post =>
+ <RestMethodCard key={post.uuid}
+ method={post}
+ selectElement={props.selectElement}
+ deleteElement={props.deleteElement}
+ />)}
+ {rest.put?.map(put =>
+ <RestMethodCard key={put.uuid}
+ method={put}
+ selectElement={props.selectElement}
+ deleteElement={props.deleteElement}
+ />)}
+ {rest.patch?.map(patch =>
+ <RestMethodCard key={patch.uuid}
+ method={patch}
+ selectElement={props.selectElement}
+ deleteElement={props.deleteElement}
+ />)}
+ {rest.delete?.map(del =>
+ <RestMethodCard key={del.uuid}
+ method={del}
+ selectElement={props.selectElement}
+ deleteElement={props.deleteElement}
+ />)}
+ {rest.head?.map(head =>
+ <RestMethodCard key={head.uuid}
+ method={head}
+ selectElement={props.selectElement}
+ deleteElement={props.deleteElement}
+ />)}
+ </div>
+ </div>
+ )
}
diff --git a/karavan-designer/src/designer/rest/RestConfigurationCard.tsx b/karavan-designer/src/designer/rest/RestConfigurationCard.tsx
index c05dc4ff..8c7ba2ff 100644
--- a/karavan-designer/src/designer/rest/RestConfigurationCard.tsx
+++ b/karavan-designer/src/designer/rest/RestConfigurationCard.tsx
@@ -17,52 +17,43 @@
import React from 'react';
import {Button} from '@patternfly/react-core';
import '../karavan.css';
-import {CamelElement, Integration} from "karavan-core/lib/model/IntegrationDefinition";
+import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-circle-icon";
import {RestConfigurationDefinition} from "karavan-core/lib/model/CamelDefinition";
interface Props {
restConfig: RestConfigurationDefinition
selectedRestConfig?: CamelElement
- integration: Integration
selectElement: (element: CamelElement) => void
deleteElement: (element: CamelElement) => void
}
-interface State {
- restConfig: RestConfigurationDefinition
- expanded: boolean
-}
-
-export class RestConfigurationCard extends React.Component<Props, State> {
+export function RestConfigurationCard (props: Props) {
- public state: State = {
- restConfig: this.props.restConfig,
- expanded: false
- };
-
- selectElement = (evt: React.MouseEvent) => {
+ function selectElement(evt: React.MouseEvent) {
evt.stopPropagation();
- this.props.selectElement.call(this, this.state.restConfig);
+ props.selectElement(props.restConfig);
}
- delete = (evt: React.MouseEvent) => {
+ function onDelete(evt: React.MouseEvent) {
evt.stopPropagation();
- this.props.deleteElement.call(this, this.props.restConfig);
+ props.deleteElement(props.restConfig);
}
- render() {
- const restConfig = this.state.restConfig;
- const desc = restConfig.host && restConfig.port
- ? restConfig.host + ":" + restConfig.port
- : (restConfig.host ? restConfig.host : "") + (restConfig.port ? restConfig.port : "");
- return (
- <div className={this.props.selectedRestConfig?.uuid === restConfig.uuid ? "rest-config-card rest-config-card-selected" : "rest-config-card rest-config-card-unselected"} onClick={e => this.selectElement(e)}>
- <div className="title">Configuration</div>
- <div className="title">{restConfig.contextPath}</div>
- <div className="description">{desc}</div>
- <Button variant="link" className="delete-button" onClick={e => this.delete(e)}><DeleteIcon/></Button>
- </div>
- );
- }
+ const restConfig = props.restConfig;
+ const desc = restConfig.host && restConfig.port
+ ? restConfig.host + ":" + restConfig.port
+ : (restConfig.host ? restConfig.host : "") + (restConfig.port ? restConfig.port : "");
+ return (
+ <div className={props.selectedRestConfig?.uuid === restConfig.uuid ? "rest-config-card rest-config-card-selected" : "rest-config-card rest-config-card-unselected"}
+ onClick={e => selectElement(e)}>
+ <div className="title">Configuration</div>
+ <div className="title">{restConfig.contextPath}</div>
+ <div className="description">{desc}</div>
+ <Button variant="link" className="delete-button"
+ onClick={e => onDelete(e)}>
+ <DeleteIcon/>
+ </Button>
+ </div>
+ )
}
diff --git a/karavan-designer/src/designer/rest/RestDesigner.tsx b/karavan-designer/src/designer/rest/RestDesigner.tsx
index d13d07e6..7d1508ff 100644
--- a/karavan-designer/src/designer/rest/RestDesigner.tsx
+++ b/karavan-designer/src/designer/rest/RestDesigner.tsx
@@ -20,9 +20,8 @@ import {
PageSection
} from '@patternfly/react-core';
import '../karavan.css';
-import {Integration, CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
+import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
import {DslProperties} from "../route/DslProperties";
-import {RouteToCreate} from "../utils/CamelUi";
import {RestCard} from "./RestCard";
import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon";
import {RestConfigurationDefinition, RestContextRefDefinition, RestDefinition} from "karavan-core/lib/model/CamelDefinition";
@@ -33,259 +32,205 @@ import {DslMetaModel} from "../utils/DslMetaModel";
import {CamelDefinitionApi} from "karavan-core/lib/api/CamelDefinitionApi";
import {RestConfigurationCard} from "./RestConfigurationCard";
import {v4 as uuidv4} from "uuid";
+import {useDesignerStore, useIntegrationStore, useSelectorStore} from "../KaravanStore";
+import {shallow} from "zustand/shallow";
-interface Props {
- onSave?: (integration: Integration, propertyOnly: boolean) => void
- integration: Integration
- dark: boolean
-}
-
-interface State {
- integration: Integration
- selectedStep?: CamelElement
- key: string
- showSelector: boolean
- showDeleteConfirmation: boolean
- propertyOnly: boolean
-}
-
-export class RestDesigner extends React.Component<Props, State> {
-
- public state: State = {
- integration: this.props.integration,
- key: "",
- showSelector: false,
- showDeleteConfirmation: false,
- propertyOnly: false
- };
-
- componentDidUpdate = (prevProps: Readonly<Props>, prevState: Readonly<State>, snapshot?: any) => {
- if (prevState.key !== this.state.key) {
- this.props.onSave?.call(this, this.state.integration, this.state.propertyOnly);
- }
- }
+export function RestDesigner() {
- onIntegrationUpdate = (i: Integration) => {
- this.setState({integration: i, showSelector: false, key: Math.random().toString(), propertyOnly: false});
- }
-
- selectElement = (element: CamelElement) => {
- this.setState({selectedStep: element})
- }
+ const [integration, setIntegration] = useIntegrationStore((state) => [state.integration, state.setIntegration], shallow)
+ const [dark, selectedStep, showDeleteConfirmation, setShowDeleteConfirmation, setPosition, width, height, top, left, hideLogDSL, setSelectedStep] = useDesignerStore((s) =>
+ [s.dark, s.selectedStep, s.showDeleteConfirmation, s.setShowDeleteConfirmation, s.setPosition, s.width, s.height, s.top, s.left, s.hideLogDSL, s.setSelectedStep], shallow)
- onPropertyUpdate = (element: CamelElement, newRoute?: RouteToCreate) => {
- if (newRoute) {
- let i = CamelDefinitionApiExt.updateIntegrationRestElement(this.state.integration, element);
- const f = CamelDefinitionApi.createFromDefinition({uri: newRoute.componentName + ":" + newRoute.name})
- const r = CamelDefinitionApi.createRouteDefinition({from: f, id: newRoute.name})
- i = CamelDefinitionApiExt.addStepToIntegration(i, r, '');
- const clone = CamelUtil.cloneIntegration(i);
- this.setState({
- integration: clone,
- key: Math.random().toString(),
- showSelector: false,
- selectedStep: element,
- propertyOnly: false
- });
- } else {
- const clone = CamelUtil.cloneIntegration(this.state.integration);
- const i = CamelDefinitionApiExt.updateIntegrationRestElement(clone, element);
- this.setState({integration: i, propertyOnly: true, key: Math.random().toString()});
- }
+ const [showSelector, setShowSelector] = useSelectorStore((s) => [s.showSelector, s.setShowSelector], shallow)
+
+ function selectElement (element: CamelElement) {
+ setSelectedStep(element);
}
- unselectElement = (evt: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
+ function unselectElement (evt: React.MouseEvent<HTMLDivElement, MouseEvent>) {
if ((evt.target as any).dataset.click === 'REST') {
evt.stopPropagation()
- this.setState({selectedStep: undefined,})
+ setSelectedStep(undefined);
}
}
- addRest = (rest: RestDefinition) => {
- const clone = CamelUtil.cloneIntegration(this.state.integration);
+ function addRest (rest: RestDefinition) {
+ const clone = CamelUtil.cloneIntegration(integration);
const i = CamelDefinitionApiExt.addRestToIntegration(clone, rest);
- this.setState({integration: i, propertyOnly: false, key: Math.random().toString(), selectedStep: rest});
+ setIntegration(i, false);
+ setSelectedStep(rest);
}
- createRest = () => {
- this.addRest(new RestDefinition());
+ function createRest () {
+ addRest(new RestDefinition());
}
- createRestConfiguration = () => {
- this.addRest(new RestConfigurationDefinition());
+ function createRestConfiguration () {
+ addRest(new RestConfigurationDefinition());
}
- showDeleteConfirmation = (element: CamelElement) => {
- this.setState({selectedStep: element, showSelector: false, showDeleteConfirmation: true});
+ function onShowDeleteConfirmation (element: CamelElement) {
+ setSelectedStep(element);
+ setShowDeleteConfirmation(true);
}
- deleteElement = () => {
- const step = this.state.selectedStep;
- if (step) {
+ function deleteElement () {
+ if (selectedStep) {
let i;
- if (step.dslName === 'RestDefinition') i = CamelDefinitionApiExt.deleteRestFromIntegration(this.state.integration, step.uuid);
- else if (step.dslName === 'RestConfigurationDefinition') i = CamelDefinitionApiExt.deleteRestConfigurationFromIntegration(this.state.integration);
- else i = CamelDefinitionApiExt.deleteRestMethodFromIntegration(this.state.integration, step.uuid);
- this.setState({
- integration: i,
- showSelector: false,
- showDeleteConfirmation: false,
- key: Math.random().toString(),
- selectedStep: undefined,
- propertyOnly: false
- });
+ if (selectedStep.dslName === 'RestDefinition') i = CamelDefinitionApiExt.deleteRestFromIntegration(integration, selectedStep.uuid);
+ else if (selectedStep.dslName === 'RestConfigurationDefinition') i = CamelDefinitionApiExt.deleteRestConfigurationFromIntegration(integration);
+ else i = CamelDefinitionApiExt.deleteRestMethodFromIntegration(integration, selectedStep.uuid);
+ setIntegration(i, false);
+ setSelectedStep(undefined);
+ setShowDeleteConfirmation(false);
}
}
- getDeleteConfirmation() {
+ function getDeleteConfirmation() {
return (<Modal
className="modal-delete"
title="Confirmation"
- isOpen={this.state.showDeleteConfirmation}
- onClose={() => this.setState({showDeleteConfirmation: false})}
+ isOpen={showDeleteConfirmation}
+ onClose={() => setShowDeleteConfirmation(false)}
actions={[
- <Button key="confirm" variant="primary" onClick={e => this.deleteElement()}>Delete</Button>,
+ <Button key="confirm" variant="primary" onClick={e => deleteElement()}>Delete</Button>,
<Button key="cancel" variant="link"
- onClick={e => this.setState({showDeleteConfirmation: false})}>Cancel</Button>
+ onClick={e => setShowDeleteConfirmation(false)}>Cancel</Button>
]}
- onEscapePress={e => this.setState({showDeleteConfirmation: false})}>
+ onEscapePress={e => setShowDeleteConfirmation(false)}>
<div>
Delete element from integration?
</div>
</Modal>)
}
- closeMethodSelector = () => {
- this.setState({showSelector: false})
+ function closeMethodSelector () {
+ setShowSelector(false);
}
- onMethodSelect = (method: DslMetaModel) => {
- if (this.state.selectedStep) {
- const clone = CamelUtil.cloneIntegration(this.state.integration);
+ function onMethodSelect (method: DslMetaModel) {
+ if (selectedStep) {
+ const clone = CamelUtil.cloneIntegration(integration);
const m = CamelDefinitionApi.createStep(method.dsl, {});
- const i = CamelDefinitionApiExt.addRestMethodToIntegration(clone, m, this.state.selectedStep?.uuid);
- this.setState({integration: i, key: Math.random().toString(), selectedStep: m, showSelector: false});
+ const i = CamelDefinitionApiExt.addRestMethodToIntegration(clone, m, selectedStep?.uuid);
+ setIntegration(i, false);
+ setSelectedStep(m);
+ setShowSelector(false);
}
}
- cloneRest = (rest: CamelElement) => {
+ function cloneRest (rest: CamelElement) {
if (rest.dslName === 'RestDefinition'){
const cloneRest = CamelUtil.cloneStep(rest);
cloneRest.uuid = uuidv4();
- const cloneIntegration = CamelUtil.cloneIntegration(this.state.integration);
+ const cloneIntegration = CamelUtil.cloneIntegration(integration);
const i = CamelDefinitionApiExt.addRestToIntegration(cloneIntegration, cloneRest);
- this.setState({integration: i, propertyOnly: false, key: Math.random().toString(), selectedStep: cloneRest});
+ setIntegration(i, false);
+ setSelectedStep(cloneRest);
} else if (rest.dslName === 'RestConfigurationDefinition') {
// could be only one RestConfigurationDefinition
- } else if (this.state.selectedStep) {
- const parentId = CamelDefinitionApiExt.findRestMethodParent(this.state.integration, rest);
+ } else if (selectedStep) {
+ const parentId = CamelDefinitionApiExt.findRestMethodParent(integration, rest);
if (parentId){
const cloneRest = CamelUtil.cloneStep(rest);
cloneRest.uuid = uuidv4();
- const cloneIntegration = CamelUtil.cloneIntegration(this.state.integration);
+ const cloneIntegration = CamelUtil.cloneIntegration(integration);
const i = CamelDefinitionApiExt.addRestMethodToIntegration(cloneIntegration, cloneRest, parentId);
- this.setState({integration: i, key: Math.random().toString(), selectedStep: cloneRest, showSelector: false});
+ setIntegration(i, false);
+ setSelectedStep(cloneRest);
}
}
}
- selectMethod = (element: CamelElement) => {
- this.setState({selectedStep: element, showSelector: true})
+ function selectMethod (element: CamelElement) {
+ setSelectedStep(element);
+ setShowSelector(true);
}
- getSelectorModal() {
+ function getSelectorModal() {
return (
<Modal
title="Select method"
width={'90%'}
className='dsl-modal'
- isOpen={this.state.showSelector}
- onClose={() => this.closeMethodSelector()}
+ isOpen={showSelector}
+ onClose={() => closeMethodSelector()}
actions={{}}>
- <RestMethodSelector
- dark={this.props.dark}
- onMethodSelect={this.onMethodSelect}/>
+ <RestMethodSelector onMethodSelect={onMethodSelect}/>
</Modal>)
}
- getRestConfigurationCard(config: RestContextRefDefinition) {
+ function getRestConfigurationCard(config: RestContextRefDefinition) {
return (<>
<RestConfigurationCard key={Math.random().toString()}
- selectedRestConfig={this.state.selectedStep}
+ selectedRestConfig={selectedStep}
restConfig={config}
- integration={this.props.integration}
- selectElement={this.selectElement}
- deleteElement={this.showDeleteConfirmation}/>
+ selectElement={selectElement}
+ deleteElement={onShowDeleteConfirmation}/>
</>)
}
- getRestCards(data: RestDefinition[]) {
+ function getRestCards(data: RestDefinition[]) {
return (<>
- {data?.map(rest => <RestCard key={rest.uuid + this.state.key}
- selectedStep={this.state.selectedStep}
+ {data?.map((rest, index) =>
+ <RestCard key={rest.uuid + index}
+ selectedStep={selectedStep}
rest={rest}
- integration={this.props.integration}
- selectMethod={this.selectMethod}
- selectElement={this.selectElement}
- deleteElement={this.showDeleteConfirmation}/>)}
+ integration={integration}
+ selectMethod={selectMethod}
+ selectElement={selectElement}
+ deleteElement={onShowDeleteConfirmation}
+ />
+ )}
</>)
}
- getPropertiesPanel() {
+ function getPropertiesPanel() {
return (
<DrawerPanelContent isResizable hasNoBorder defaultSize={'400px'} maxSize={'800px'} minSize={'300px'}>
- <DslProperties
- integration={this.props.integration}
- step={this.state.selectedStep}
- onIntegrationUpdate={this.onIntegrationUpdate}
- onPropertyUpdate={this.onPropertyUpdate}
- isRouteDesigner={false}
- onClone={this.cloneRest}
- dark={this.props.dark}/>
+ <DslProperties isRouteDesigner={false}/>
</DrawerPanelContent>
)
}
- render() {
- const data = this.props.integration.spec.flows?.filter(f => f.dslName === 'RestDefinition');
- const configData = this.props.integration.spec.flows?.filter(f => f.dslName === 'RestConfigurationDefinition');
- const config = configData && Array.isArray(configData) ? configData[0] : undefined;
- return (
- <PageSection className="rest-page" isFilled padding={{default: 'noPadding'}}>
- <div className="rest-page-columns">
- <Drawer isExpanded isInline>
- <DrawerContent panelContent={this.getPropertiesPanel()}>
- <DrawerContentBody>
- <div className="graph" data-click="REST" onClick={event => this.unselectElement(event)}>
- <div className="flows">
- {config && this.getRestConfigurationCard(config)}
- {data && this.getRestCards(data)}
- <div className="add-rest">
+ const data = integration.spec.flows?.filter(f => f.dslName === 'RestDefinition');
+ const configData = integration.spec.flows?.filter(f => f.dslName === 'RestConfigurationDefinition');
+ const config = configData && Array.isArray(configData) ? configData[0] : undefined;
+ return (
+ <PageSection className="rest-page" isFilled padding={{default: 'noPadding'}}>
+ <div className="rest-page-columns">
+ <Drawer isExpanded isInline>
+ <DrawerContent panelContent={getPropertiesPanel()}>
+ <DrawerContentBody>
+ <div className="graph" data-click="REST" onClick={event => unselectElement(event)}>
+ <div className="flows">
+ {config && getRestConfigurationCard(config)}
+ {data && getRestCards(data)}
+ <div className="add-rest">
+ <Button
+ variant={data?.length === 0 ? "primary" : "secondary"}
+ data-click="ADD_REST"
+ icon={<PlusIcon/>}
+ onClick={e => createRest()}>Create service
+ </Button>
+ {config === undefined &&
<Button
- variant={data?.length === 0 ? "primary" : "secondary"}
- data-click="ADD_REST"
+ variant="secondary"
+ data-click="ADD_REST_REST_CONFIG"
icon={<PlusIcon/>}
- onClick={e => this.createRest()}>Create service
+ onClick={e => createRestConfiguration()}>Create configuration
</Button>
- {config === undefined &&
- <Button
- variant="secondary"
- data-click="ADD_REST_REST_CONFIG"
- icon={<PlusIcon/>}
- onClick={e => this.createRestConfiguration()}>Create configuration
- </Button>
- }
- </div>
+ }
</div>
</div>
- </DrawerContentBody>
- </DrawerContent>
- </Drawer>
- </div>
- {this.getSelectorModal()}
- {this.getDeleteConfirmation()}
- </PageSection>
- )
- }
+ </div>
+ </DrawerContentBody>
+ </DrawerContent>
+ </Drawer>
+ </div>
+ {getSelectorModal()}
+ {getDeleteConfirmation()}
+ </PageSection>
+ )
}
diff --git a/karavan-designer/src/designer/rest/RestMethodCard.tsx b/karavan-designer/src/designer/rest/RestMethodCard.tsx
index b1a5cdc1..f27e364c 100644
--- a/karavan-designer/src/designer/rest/RestMethodCard.tsx
+++ b/karavan-designer/src/designer/rest/RestMethodCard.tsx
@@ -17,50 +17,40 @@
import React from 'react';
import {Button} from '@patternfly/react-core';
import '../karavan.css';
-import {CamelElement, Integration} from "karavan-core/lib/model/IntegrationDefinition";
+import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-circle-icon";
+import {useDesignerStore} from "../KaravanStore";
+import {shallow} from "zustand/shallow";
-interface Props<T> {
+interface Props<T extends CamelElement> {
method: T
- selectedStep?: CamelElement
- integration: Integration
selectElement: (element: CamelElement) => void
deleteElement: (element: CamelElement) => void
}
-interface State<T> {
- method: T
- expanded: boolean
-}
-
-export class RestMethodCard extends React.Component<Props<any>, State<any>> {
+export function RestMethodCard<T extends CamelElement> (props: Props<T>) {
- public state: State<any> = {
- method: this.props.method,
- expanded: false
- };
+ const [selectedStep] = useDesignerStore((s) => [s.selectedStep], shallow)
- selectElement = (evt: React.MouseEvent) => {
+ function selectElement (evt: React.MouseEvent) {
evt.stopPropagation();
- this.props.selectElement.call(this, this.state.method);
+ props.selectElement(props.method);
}
- delete = (evt: React.MouseEvent) => {
+ function onDelete (evt: React.MouseEvent) {
evt.stopPropagation();
- this.props.deleteElement.call(this, this.props.method);
+ props.deleteElement(props.method);
}
- render() {
- const method = this.state.method;
- return (
- <div className={this.props.selectedStep?.uuid === method.uuid ? "method-card method-card-selected" : "method-card method-card-unselected"} onClick={e => this.selectElement(e)}>
- <div className="method">{method.dslName.replace('Definition', '').toUpperCase()}</div>
- <div className="rest-method-desc">
- <div className="title">{method.path}</div>
- <div className="description">{method.description}</div>
- </div>
- <Button variant="link" className="delete-button" onClick={e => this.delete(e)}><DeleteIcon/></Button>
+ const method: any = props.method;
+ return (
+ <div className={selectedStep?.uuid === method.uuid ? "method-card method-card-selected" : "method-card method-card-unselected"} onClick={e => selectElement(e)}>
+ <div className="method">{method.dslName.replace('Definition', '').toUpperCase()}</div>
+ <div className="rest-method-desc">
+ <div className="title">{method.path}</div>
+ <div className="description">{method.description}</div>
</div>
- );
- }
-}
+ <Button variant="link" className="delete-button" onClick={e => onDelete(e)}><DeleteIcon/></Button>
+ </div>
+ )
+}
\ No newline at end of file
diff --git a/karavan-designer/src/designer/rest/RestMethodSelector.tsx b/karavan-designer/src/designer/rest/RestMethodSelector.tsx
index d3c4ec66..bbd4cccb 100644
--- a/karavan-designer/src/designer/rest/RestMethodSelector.tsx
+++ b/karavan-designer/src/designer/rest/RestMethodSelector.tsx
@@ -24,34 +24,26 @@ import {
import '../karavan.css';
import {CamelUi} from "../utils/CamelUi";
import {DslMetaModel} from "../utils/DslMetaModel";
+import {useDesignerStore} from "../KaravanStore";
+import {shallow} from "zustand/shallow";
interface Props {
onMethodSelect: (method: DslMetaModel) => void
- dark: boolean
}
-interface State {
-}
-
-export class RestMethodSelector extends React.Component<Props, State> {
-
- public state: State = {};
-
-
- selectTab = (evt: React.MouseEvent<HTMLElement, MouseEvent>, eventKey: string | number) => {
- this.setState({tabIndex: eventKey})
- }
+export function RestMethodSelector(props: Props) {
+ const [dark] = useDesignerStore((s) => [s.dark], shallow)
- selectMethod = (evt: React.MouseEvent, method: any) => {
+ function selectMethod (evt: React.MouseEvent, method: any) {
evt.stopPropagation()
- this.props.onMethodSelect.call(this, method);
+ props.onMethodSelect(method);
}
- getCard(dsl: DslMetaModel, index: number) {
+ function getCard(dsl: DslMetaModel, index: number) {
return (
<Card key={dsl.dsl + index} isCompact className="dsl-card"
- onClick={event => this.selectMethod(event, dsl)}>
+ onClick={event => selectMethod(event, dsl)}>
<CardHeader>
{CamelUi.getIconForDsl(dsl)}
<Text>{dsl.title}</Text>
@@ -74,17 +66,15 @@ export class RestMethodSelector extends React.Component<Props, State> {
)
}
- render() {
- return (
- <PageSection variant={this.props.dark ? "darker" : "light"}>
- <Tabs style={{overflow: 'hidden'}} activeKey="methods" onSelect={this.selectTab}>
- <Tab eventKey="methods" title={<TabTitleText>Methods</TabTitleText>}>
- <Gallery hasGutter className="dsl-gallery">
- {CamelUi.getSelectorRestMethodModels().map((dsl: DslMetaModel, index: number) => this.getCard(dsl, index))}
- </Gallery>
- </Tab>
- </Tabs>
- </PageSection>
- );
- }
+ return (
+ <PageSection variant={dark ? "darker" : "light"}>
+ <Tabs style={{overflow: 'hidden'}} activeKey="methods" onSelect={event => {}}>
+ <Tab eventKey="methods" title={<TabTitleText>Methods</TabTitleText>}>
+ <Gallery hasGutter className="dsl-gallery">
+ {CamelUi.getSelectorRestMethodModels().map((dsl: DslMetaModel, index: number) => getCard(dsl, index))}
+ </Gallery>
+ </Tab>
+ </Tabs>
+ </PageSection>
+ )
}
\ No newline at end of file
diff --git a/karavan-designer/src/designer/route/DeleteConfirmation.tsx b/karavan-designer/src/designer/route/DeleteConfirmation.tsx
new file mode 100644
index 00000000..999a4752
--- /dev/null
+++ b/karavan-designer/src/designer/route/DeleteConfirmation.tsx
@@ -0,0 +1,51 @@
+/*
+ * 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 from 'react';
+import {
+ Button, Modal,
+} from '@patternfly/react-core';
+import '../karavan.css';
+import {useRouteDesignerHook} from "./useRouteDesignerHook";
+import {useDesignerStore} from "../KaravanStore";
+import {shallow} from "zustand/shallow";
+
+export function DeleteConfirmation() {
+
+
+ const {deleteElement} = useRouteDesignerHook();
+
+ const [showDeleteConfirmation, deleteMessage , setShowDeleteConfirmation] =
+ useDesignerStore((s) => [s.showDeleteConfirmation, s.deleteMessage, s.setShowDeleteConfirmation], shallow)
+
+ return (
+ <Modal
+ className="modal-delete"
+ title="Confirmation"
+ isOpen={showDeleteConfirmation}
+ onClose={() => setShowDeleteConfirmation(false)}
+ actions={[
+ <Button key="confirm" variant="primary" onClick={e => deleteElement()}>Delete</Button>,
+ <Button key="cancel" variant="link"
+ onClick={e => setShowDeleteConfirmation(false)}>Cancel</Button>
+ ]}
+ onEscapePress={e => setShowDeleteConfirmation(false)}>
+ <div>
+ {deleteMessage}
+ </div>
+ </Modal>
+ )
+}
\ No newline at end of file
diff --git a/karavan-designer/src/designer/route/DslConnections.tsx b/karavan-designer/src/designer/route/DslConnections.tsx
index 938042d4..9f3578ad 100644
--- a/karavan-designer/src/designer/route/DslConnections.tsx
+++ b/karavan-designer/src/designer/route/DslConnections.tsx
@@ -14,67 +14,52 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import React from 'react';
+import React, {useEffect} from 'react';
import '../karavan.css';
-import {Integration, CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
+import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
import {DslPosition, EventBus} from "../utils/EventBus";
import {CamelUi} from "../utils/CamelUi";
-import {Subscription} from "rxjs";
import {SagaDefinition} from "karavan-core/lib/model/CamelDefinition";
-
-interface Props {
- integration: Integration
- width: number
- height: number
- top: number
- left: number
-}
-
-interface State {
- integration: Integration
- steps: Map<string, DslPosition>
-}
+import {useConnectionsStore, useDesignerStore, useIntegrationStore} from "../KaravanStore";
+import {shallow} from "zustand/shallow";
+import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
const overlapGap: number = 40;
const outgoingDefinitions: string[] = ['ToDefinition', 'KameletDefinition', 'ToDynamicDefinition', "PollEnrichDefinition", "EnrichDefinition", "WireTapDefinition", "SagaDefinition"];
+export function DslConnections() {
-export class DslConnections extends React.Component<Props, State> {
+ const [integration] = useIntegrationStore((state) => [state.integration], shallow)
+ const [width, height, top, left] = useDesignerStore((s) =>
+ [s.width, s.height, s.top, s.left], shallow)
+ const [ steps, addStep, deleteStep, clearSteps] = useConnectionsStore((s) => [s.steps, s.addStep, s.deleteStep, s.clearSteps], shallow)
- public state: State = {
- integration: this.props.integration,
- steps: new Map<string, DslPosition>(),
- };
- sub?: Subscription;
+ useEffect(() => {
+ const sub = EventBus.onPosition()?.subscribe((evt: DslPosition) => setPosition(evt));
+ return () => {
+ sub?.unsubscribe();
+ };
+ });
- componentDidMount() {
- this.sub = EventBus.onPosition()?.subscribe((evt: DslPosition) => this.setPosition(evt));
- }
-
- componentWillUnmount() {
- this.sub?.unsubscribe();
- }
+ useEffect(() => {
+ const toDelete: string[] = Array.from(steps.keys()).filter(k => CamelDefinitionApiExt.findElementInIntegration(integration, k) === undefined);
+ toDelete.forEach(key => deleteStep(key));
+ }, [integration]);
- setPosition(evt: DslPosition) {
+ function setPosition(evt: DslPosition) {
if (evt.command === "add") {
- this.setState(prevState => ({steps: prevState.steps.set(evt.step.uuid, evt)}));
+ addStep(evt.step.uuid, evt);
+ }
+ else if (evt.command === "delete") {
+ deleteStep(evt.step.uuid);
+ }
+ else if (evt.command === "clean") {
+ clearSteps();
}
- else if (evt.command === "delete") this.setState(prevState => {
- prevState.steps.clear();
- Array.from(prevState.steps.entries())
- .filter(value => value[1]?.parent?.uuid !== evt.step.uuid)
- .forEach(value => prevState.steps.set(value[0], value[1]));
- prevState.steps.delete(evt.step.uuid);
- return {steps: prevState.steps};
- });
- else if (evt.command === "clean") this.setState(prevState => {
- prevState.steps.clear();
- return {steps: prevState.steps};
- });
}
- getIncomings() {
- let outs: [string, number][] = Array.from(this.state.steps.values())
+ function getIncomings() {
+ let outs: [string, number][] = Array.from(steps.values())
.filter(pos => ["FromDefinition"].includes(pos.step.dslName))
.filter(pos => !CamelUi.isElementInternalComponent(pos.step))
.filter(pos => !(pos.step.dslName === 'FromDefinition' && CamelUi.hasInternalUri(pos.step)))
@@ -84,17 +69,17 @@ export class DslConnections extends React.Component<Props, State> {
return y1 > y2 ? 1 : -1
})
.map(pos => [pos.step.uuid, pos.headerRect.y]);
- while (this.hasOverlap(outs)) {
- outs = this.addGap(outs);
+ while (hasOverlap(outs)) {
+ outs = addGap(outs);
}
return outs;
}
- getIncoming(data: [string, number]) {
- const pos = this.state.steps.get(data[0]);
+ function getIncoming(data: [string, number]) {
+ const pos = steps.get(data[0]);
if (pos) {
- const fromX = pos.headerRect.x + pos.headerRect.width / 2 - this.props.left;
- const fromY = pos.headerRect.y + pos.headerRect.height / 2 - this.props.top;
+ const fromX = pos.headerRect.x + pos.headerRect.width / 2 - left;
+ const fromY = pos.headerRect.y + pos.headerRect.height / 2 - top;
const r = pos.headerRect.height / 2;
const incomingX = 20;
@@ -103,8 +88,6 @@ export class DslConnections extends React.Component<Props, State> {
const lineX2 = fromX - r * 2 + 7;
const lineY2 = fromY;
- const imageX = incomingX - r + 5;
- const imageY = fromY - r + 5;
return (
<g key={pos.step.uuid + "-incoming"}>
<circle cx={incomingX} cy={fromY} r={r} className="circle-incoming"/>
@@ -117,10 +100,10 @@ export class DslConnections extends React.Component<Props, State> {
}
}
- getIncomingIcons(data: [string, number]) {
- const pos = this.state.steps.get(data[0]);
+ function getIncomingIcons(data: [string, number]) {
+ const pos = steps.get(data[0]);
if (pos) {
- const fromY = pos.headerRect.y + pos.headerRect.height / 2 - this.props.top;
+ const fromY = pos.headerRect.y + pos.headerRect.height / 2 - top;
const r = pos.headerRect.height / 2;
const incomingX = 20;
const imageX = incomingX - r + 5;
@@ -133,7 +116,7 @@ export class DslConnections extends React.Component<Props, State> {
}
}
- hasOverlap(data: [string, number][]): boolean {
+ function hasOverlap(data: [string, number][]): boolean {
let result = false;
data.forEach((d, i, arr) => {
if (i > 0 && d[1] - arr[i - 1][1] < overlapGap) result = true;
@@ -141,7 +124,7 @@ export class DslConnections extends React.Component<Props, State> {
return result;
}
- addGap(data: [string, number][]): [string, number][] {
+ function addGap(data: [string, number][]): [string, number][] {
const result: [string, number][] = [];
data.forEach((d, i, arr) => {
if (i > 0 && d[1] - arr[i - 1][1] < overlapGap) result.push([d[0], d[1] + overlapGap])
@@ -151,8 +134,8 @@ export class DslConnections extends React.Component<Props, State> {
}
- getOutgoings(): [string, number][] {
- let outs: [string, number][] = Array.from(this.state.steps.values())
+ function getOutgoings(): [string, number][] {
+ let outs: [string, number][] = 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 => pos.step.dslName === 'ToDefinition' && !CamelUi.isActionKamelet(pos.step) && !CamelUi.isElementInternalComponent(pos.step))
@@ -163,21 +146,21 @@ export class DslConnections extends React.Component<Props, State> {
const y2 = pos2.headerRect.y + pos2.headerRect.height / 2;
return y1 > y2 ? 1 : -1
})
- .map(pos => [pos.step.uuid, pos.headerRect.y - this.props.top]);
- while (this.hasOverlap(outs)) {
- outs = this.addGap(outs);
+ .map(pos => [pos.step.uuid, pos.headerRect.y - top]);
+ while (hasOverlap(outs)) {
+ outs = addGap(outs);
}
return outs;
}
- getOutgoing(data: [string, number]) {
- const pos = this.state.steps.get(data[0]);
+ function getOutgoing(data: [string, number]) {
+ const pos = steps.get(data[0]);
if (pos) {
- const fromX = pos.headerRect.x + pos.headerRect.width / 2 - this.props.left;
- const fromY = pos.headerRect.y + pos.headerRect.height / 2 - this.props.top;
+ const fromX = pos.headerRect.x + pos.headerRect.width / 2 - left;
+ const fromY = pos.headerRect.y + pos.headerRect.height / 2 - top;
const r = pos.headerRect.height / 2;
- const outgoingX = this.props.width - 20;
+ const outgoingX = width - 20;
const outgoingY = data[1] + 15;
const lineX1 = fromX + r;
@@ -203,11 +186,11 @@ export class DslConnections extends React.Component<Props, State> {
}
}
- getOutgoingIcons(data: [string, number]) {
- const pos = this.state.steps.get(data[0]);
+ function getOutgoingIcons(data: [string, number]) {
+ const pos = steps.get(data[0]);
if (pos) {
const r = pos.headerRect.height / 2;
- const outgoingX = this.props.width - 20;
+ const outgoingX = width - 20;
const outgoingY = data[1] + 15;
const imageX = outgoingX - r + 5;
const imageY = outgoingY - r + 5;
@@ -219,55 +202,55 @@ export class DslConnections extends React.Component<Props, State> {
}
}
- getInternals(): [string, number, boolean][] {
- let outs: [string, number, boolean][] = Array.from(this.state.steps.values())
+ function getInternals(): [string, number, boolean][] {
+ let outs: [string, number, boolean][] = Array.from(steps.values())
.filter(pos => outgoingDefinitions.includes(pos.step.dslName) && CamelUi.hasInternalUri(pos.step))
.sort((pos1: DslPosition, pos2: DslPosition) => {
const y1 = pos1.headerRect.y + pos1.headerRect.height / 2;
const y2 = pos2.headerRect.y + pos2.headerRect.height / 2;
return y1 > y2 ? 1 : -1
})
- .map(pos => [pos.step.uuid, pos.headerRect.y - this.props.top, pos.isSelected]);
+ .map(pos => [pos.step.uuid, pos.headerRect.y - top, pos.isSelected]);
return outs;
}
- getInternalLines(data: [string, number, boolean]) {
- const pos = this.state.steps.get(data[0]);
+ function getInternalLines(data: [string, number, boolean]) {
+ const pos = steps.get(data[0]);
const uri = (pos?.step as any).uri;
if (uri && uri.length && pos) {
const key = pos.step.uuid + "-outgoing"
- const fromX = pos.headerRect.x + pos.headerRect.width / 2 - this.props.left;
- const fromY = pos.headerRect.y + pos.headerRect.height / 2 - this.props.top;
+ const fromX = pos.headerRect.x + pos.headerRect.width / 2 - left;
+ const fromY = pos.headerRect.y + pos.headerRect.height / 2 - top;
const r = pos.headerRect.height / 2;
const className = (CamelUi.hasDirectUri(pos.step) ? "path-direct" : "path-seda") + (data[2] ? "-selected" : "");
- return this.getInternalLine(uri, key, className, fromX, fromY, r, data[1]);
+ return getInternalLine(uri, key, className, fromX, fromY, r, data[1]);
} else if (pos?.step.dslName === 'SagaDefinition'){
const saga = (pos?.step as SagaDefinition);
- const fromX = pos.headerRect.x + pos.headerRect.width / 2 - this.props.left;
- const fromY = pos.headerRect.y + pos.headerRect.height / 2 - this.props.top;
+ const fromX = pos.headerRect.x + pos.headerRect.width / 2 - left;
+ const fromY = pos.headerRect.y + pos.headerRect.height / 2 - top;
const r = pos.headerRect.height / 2;
const result:any[] = [];
if (saga.completion && (saga.completion.startsWith("direct") || saga.completion.startsWith("seda"))){
const key = pos.step.uuid + "-completion"
const className = saga.completion.startsWith("direct") ? "path-direct" : "path-seda";
- result.push(this.getInternalLine(saga.completion, key, className, fromX, fromY, r, data[1]));
+ result.push(getInternalLine(saga.completion, key, className, fromX, fromY, r, data[1]));
}
if (saga.compensation && (saga.compensation.startsWith("direct") || saga.compensation.startsWith("seda"))){
const key = pos.step.uuid + "-compensation"
const className = saga.compensation.startsWith("direct") ? "path-direct" : "path-seda";
- result.push(this.getInternalLine(saga.compensation, key, className, fromX, fromY, r, data[1]));
+ result.push(getInternalLine(saga.compensation, key, className, fromX, fromY, r, data[1]));
}
return result;
}
}
- getInternalLine(uri: string, key: string, className: string, fromX: number, fromY: number, r: number, i: number) {
- const target = Array.from(this.state.steps.values())
+ function getInternalLine(uri: string, key: string, className: string, fromX: number, fromY: number, r: number, i: number) {
+ const target = Array.from(steps.values())
.filter(s => s.step.dslName === 'FromDefinition')
.filter(s => (s.step as any).uri && (s.step as any).uri === uri)[0];
if (target) {
- const targetX = target.headerRect.x + target.headerRect.width / 2 - this.props.left;
- const targetY = target.headerRect.y + target.headerRect.height / 2 - this.props.top;
+ const targetX = target.headerRect.x + target.headerRect.width / 2 - left;
+ const targetY = target.headerRect.y + target.headerRect.height / 2 - top;
const gap = 100;
const add = 0.2;
@@ -294,7 +277,7 @@ export class DslConnections extends React.Component<Props, State> {
const pointX4 = pointLX + coefX;
const pointY4 = endY;
- return this.getInternalPath(key, className, startX, startY, pointX1, pointY1, pointX2, pointY2, pointLX, pointLY, pointX3, pointY3, pointX4, pointY4, endX, endY);
+ return getInternalPath(key, className, startX, startY, pointX1, pointY1, pointX2, pointY2, pointLX, pointLY, pointX3, pointY3, pointX4, pointY4, endX, endY);
} else if (targetX > fromX && targetX - fromX < gap) {
const startX = fromX - r;
const startY = fromY;
@@ -317,7 +300,7 @@ export class DslConnections extends React.Component<Props, State> {
const pointX4 = pointLX - coefX/2;
const pointY4 = endY;
- return this.getInternalPath(key, className, startX, startY, pointX1, pointY1, pointX2, pointY2, pointLX, pointLY, pointX3, pointY3, pointX4, pointY4, endX, endY);
+ return getInternalPath(key, className, startX, startY, pointX1, pointY1, pointX2, pointY2, pointLX, pointLY, pointX3, pointY3, pointX4, pointY4, endX, endY);
} else if (targetX <= fromX && fromX - targetX < gap) {
const startX = fromX + r;
const startY = fromY;
@@ -340,7 +323,7 @@ export class DslConnections extends React.Component<Props, State> {
const pointX4 = pointLX - coefX/2;
const pointY4 = endY;
- return this.getInternalPath(key, className, startX, startY, pointX1, pointY1, pointX2, pointY2, pointLX, pointLY, pointX3, pointY3, pointX4, pointY4, endX, endY);
+ return getInternalPath(key, className, startX, startY, pointX1, pointY1, pointX2, pointY2, pointLX, pointLY, pointX3, pointY3, pointX4, pointY4, endX, endY);
} else {
const startX = fromX - r;
const startY = fromY;
@@ -363,12 +346,12 @@ export class DslConnections extends React.Component<Props, State> {
const pointX4 = pointLX + coefX;
const pointY4 = endY;
- return this.getInternalPath(key, className, startX, startY, pointX1, pointY1, pointX2, pointY2, pointLX, pointLY, pointX3, pointY3, pointX4, pointY4, endX, endY);
+ return getInternalPath(key, className, startX, startY, pointX1, pointY1, pointX2, pointY2, pointLX, pointLY, pointX3, pointY3, pointX4, pointY4, endX, endY);
}
}
}
- getInternalPath(key: string, className: string, startX: number, startY: number, pointX1: number, pointY1: number, pointX2: number, pointY2: number, pointLX: number, pointLY: number,
+ function getInternalPath(key: string, className: string, startX: number, startY: number, pointX1: number, pointY1: number, pointX2: number, pointY2: number, pointLX: number, pointLY: number,
pointX3: number, pointY3: number, pointX4: number, pointY4: number, endX: number, endY: number) {
return (
<g key={key}>
@@ -380,35 +363,35 @@ export class DslConnections extends React.Component<Props, State> {
)
}
- getCircle(pos: DslPosition) {
- const cx = pos.headerRect.x + pos.headerRect.width / 2 - this.props.left;
- const cy = pos.headerRect.y + pos.headerRect.height / 2 - this.props.top;
+ function getCircle(pos: DslPosition) {
+ const cx = pos.headerRect.x + pos.headerRect.width / 2 - left;
+ const cy = pos.headerRect.y + pos.headerRect.height / 2 - top;
const r = pos.headerRect.height / 2;
return (
<circle cx={cx} cy={cy} r={r} stroke="transparent" strokeWidth="3" fill="transparent" key={pos.step.uuid + "-circle"}/>
)
}
- hasSteps = (step: CamelElement): boolean => {
+ function hasSteps (step: CamelElement): boolean {
return (step.hasSteps() && !['FromDefinition'].includes(step.dslName))
|| ['RouteDefinition', 'TryDefinition', 'ChoiceDefinition', 'SwitchDefinition'].includes(step.dslName);
}
- getPreviousStep(pos: DslPosition) {
- return Array.from(this.state.steps.values())
+ 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];
}
- getArrow(pos: DslPosition) {
- const endX = pos.headerRect.x + pos.headerRect.width / 2 - this.props.left;
- const endY = pos.headerRect.y - 9 - this.props.top;
+ function getArrow(pos: DslPosition) {
+ const endX = pos.headerRect.x + pos.headerRect.width / 2 - left;
+ const endY = pos.headerRect.y - 9 - top;
if (pos.parent) {
- const parent = this.state.steps.get(pos.parent.uuid);
+ const parent = steps.get(pos.parent.uuid);
if (parent) {
- const startX = parent.headerRect.x + parent.headerRect.width / 2 - this.props.left;
- const startY = parent.headerRect.y + parent.headerRect.height - this.props.top;
+ const startX = parent.headerRect.x + parent.headerRect.width / 2 - left;
+ const startY = parent.headerRect.y + parent.headerRect.height - top;
if ((!pos.inSteps || (pos.inSteps && pos.position === 0)) && parent.step.dslName !== 'MulticastDefinition') {
return (
<path name={pos.step.dslName} d={`M ${startX},${startY} C ${startX},${endY} ${endX},${startY} ${endX},${endY}`}
@@ -419,22 +402,22 @@ export class DslConnections extends React.Component<Props, State> {
<path d={`M ${startX},${startY} C ${startX},${endY} ${endX},${startY} ${endX},${endY}`}
className="path" key={pos.step.uuid} markerEnd="url(#arrowhead)"/>
)
- } else if (pos.inSteps && pos.position > 0 && !this.hasSteps(pos.step)) {
- const prev = this.getPreviousStep(pos);
+ } else if (pos.inSteps && pos.position > 0 && !hasSteps(pos.step)) {
+ const prev = getPreviousStep(pos);
if (prev) {
- const r = this.hasSteps(prev.step) ? prev.rect : prev.headerRect;
- const prevX = r.x + r.width / 2 - this.props.left;
- const prevY = r.y + r.height - this.props.top;
+ 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 x1={prevX} y1={prevY} x2={endX} y2={endY} className="path" key={pos.step.uuid} markerEnd="url(#arrowhead)"/>
)
}
- } else if (pos.inSteps && pos.position > 0 && this.hasSteps(pos.step)) {
- const prev = this.getPreviousStep(pos);
+ } else if (pos.inSteps && pos.position > 0 && hasSteps(pos.step)) {
+ const prev = getPreviousStep(pos);
if (prev) {
- const r = this.hasSteps(prev.step) ? prev.rect : prev.headerRect;
- const prevX = r.x + r.width / 2 - this.props.left;
- const prevY = r.y + r.height - this.props.top;
+ 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 x1={prevX} y1={prevY} x2={endX} y2={endY} className="path" key={pos.step.uuid} markerEnd="url(#arrowhead)"/>
)
@@ -444,33 +427,31 @@ export class DslConnections extends React.Component<Props, State> {
}
}
- getSvg() {
- const steps = Array.from(this.state.steps.values());
+ function getSvg() {
+ const stepsArray = Array.from(steps.values());
return (
<svg
- style={{width: this.props.width, height: this.props.height, position: "absolute", left: 0, top: 0}}
- viewBox={"0 0 " + this.props.width + " " + this.props.height}>
+ style={{width: width, height: height + 80, position: "absolute", left: 0, top: 0}}
+ viewBox={"0 0 " + (width) + " " + (height + 80)}>
<defs>
<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>
- {steps.map(pos => this.getCircle(pos))}
- {steps.map(pos => this.getArrow(pos))}
- {this.getIncomings().map(p => this.getIncoming(p))}
- {this.getOutgoings().map(p => this.getOutgoing(p))}
- {/*{this.getInternals().map((p) => this.getInternalLines(p)).flat()}*/}
+ {stepsArray.map(pos => getCircle(pos))}
+ {stepsArray.map(pos => getArrow(pos))}
+ {getIncomings().map(p => getIncoming(p))}
+ {getOutgoings().map(p => getOutgoing(p))}
+ {/*{getInternals().map((p) => getInternalLines(p)).flat()}*/}
</svg>
)
}
- render() {
- return (
- <div className="connections" style={{width: this.props.width, height: this.props.height, marginTop: "8px"}}>
- {this.getSvg()}
- {this.getIncomings().map(p => this.getIncomingIcons(p))}
- {this.getOutgoings().map(p => this.getOutgoingIcons(p))}
- </div>
- );
- }
+ return (
+ <div id="connections" className="connections" style={{ width: width, height: height + 80}}>
+ {getSvg()}
+ {getIncomings().map(p => getIncomingIcons(p))}
+ {getOutgoings().map(p => getOutgoingIcons(p))}
+ </div>
+ )
}
diff --git a/karavan-designer/src/designer/route/DslElement.tsx b/karavan-designer/src/designer/route/DslElement.tsx
index 2ea1aaa2..f3283154 100644
--- a/karavan-designer/src/designer/route/DslElement.tsx
+++ b/karavan-designer/src/designer/route/DslElement.tsx
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import React, {CSSProperties} from 'react';
+import React, {CSSProperties, useEffect, useMemo, useRef, useState} from 'react';
import {
Button,
Flex,
@@ -25,147 +25,133 @@ import '../karavan.css';
import AddIcon from "@patternfly/react-icons/dist/js/icons/plus-circle-icon";
import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-circle-icon";
import InsertIcon from "@patternfly/react-icons/dist/js/icons/arrow-alt-circle-right-icon";
-import {CamelElement, Integration} from "karavan-core/lib/model/IntegrationDefinition";
+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 ReactDOM from "react-dom";
import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
import {CamelDisplayUtil} from "karavan-core/lib/api/CamelDisplayUtil";
+import {useDesignerStore, useIntegrationStore} from "../KaravanStore";
+import {shallow} from "zustand/shallow";
+import {useRouteDesignerHook} from "./useRouteDesignerHook";
interface Props {
- integration: Integration,
step: CamelElement,
parent: CamelElement | undefined,
- deleteElement: any
- selectElement: any
- openSelector: (parentId: string | undefined, parentDsl: string | undefined, showSteps: boolean, position?: number | undefined) => void
- moveElement: (source: string, target: string, asChild: boolean) => void
- selectedUuid: string []
inSteps: boolean
position: number
}
-interface State {
- showSelector: boolean
- showMoveConfirmation: boolean
- moveElements: [string | undefined, string | undefined]
- tabIndex: string | number
- isDragging: boolean
- isDraggedOver: boolean
-}
+export function DslElement(props: Props) {
+
+ const headerRef = React.useRef<HTMLDivElement>(null);
+ const {selectElement, moveElement, onShowDeleteConfirmation, openSelector} = useRouteDesignerHook();
+
+ const [integration] = useIntegrationStore((s) => [s.integration, s.setIntegration], shallow)
-export class DslElement extends React.Component<Props, State> {
-
- public state: State = {
- showSelector: false,
- showMoveConfirmation: false,
- moveElements: [undefined, undefined],
- tabIndex: 0,
- isDragging: false,
- isDraggedOver: false,
- };
-
- //
- // componentDidUpdate = (prevProps: Readonly<Props>, prevState: Readonly<State>, snapshot?: any) => {
- // if (prevState.selectedUuid !== this.props.selectedUuid) {
- // this.setState({selectedUuid: this.props.selectedUuid});
- // }
- // }
-
- openSelector = (evt: React.MouseEvent, showSteps: boolean = true, isInsert: boolean = false) => {
+ const [selectedUuids, selectedStep, showMoveConfirmation, setShowMoveConfirmation, hideLogDSL] =
+ useDesignerStore((s) =>
+ [s.selectedUuids, s.selectedStep, s.showMoveConfirmation, s.setShowMoveConfirmation, s.hideLogDSL], shallow)
+ const [isDragging, setIsDragging] = useState<boolean>(false);
+
+ const [isDraggedOver, setIsDraggedOver] = useState<boolean>(false);
+ const [moveElements, setMoveElements] = useState<[string | undefined, string | undefined]>([undefined, undefined]);
+
+ function onOpenSelector(evt: React.MouseEvent, showSteps: boolean = true, isInsert: boolean = false) {
evt.stopPropagation();
- if (isInsert && this.props.parent) {
- this.props.openSelector.call(this, this.props.parent.uuid, this.props.parent.dslName, showSteps, this.props.position);
+ if (isInsert && props.parent) {
+ openSelector(props.parent.uuid, props.parent.dslName, showSteps, props.position);
} else {
- this.props.openSelector.call(this, this.props.step.uuid, this.props.step.dslName, showSteps);
+ openSelector(props.step.uuid, props.step.dslName, showSteps);
}
}
- closeDslSelector = () => {
- this.setState({showSelector: false})
- }
-
- delete = (evt: React.MouseEvent) => {
+ function onDeleteElement(evt: React.MouseEvent) {
evt.stopPropagation();
- this.props.deleteElement.call(this, this.props.step.uuid);
+ onShowDeleteConfirmation(props.step.uuid);
}
- selectElement = (evt: React.MouseEvent) => {
+ function onSelectElement(evt: React.MouseEvent) {
evt.stopPropagation();
- this.props.selectElement.call(this, this.props.step);
+ selectElement(props.step);
}
- dragElement = (event: React.DragEvent<HTMLDivElement>, element: CamelElement) => {
+ function dragElement(event: React.DragEvent<HTMLDivElement>, element: CamelElement) {
event.preventDefault();
event.stopPropagation();
- this.setState({isDraggedOver: false});
+ setIsDraggedOver(false);
const sourceUuid = event.dataTransfer.getData("text/plain");
const targetUuid = element.uuid;
if (sourceUuid !== targetUuid) {
- if (element.hasSteps()){
- this.setState({showMoveConfirmation: true, moveElements: [sourceUuid, targetUuid]});
+ if (element.hasSteps()) {
+ setShowMoveConfirmation(true);
+ setMoveElements([sourceUuid, targetUuid])
} else {
- this.props.moveElement?.call(this, sourceUuid, targetUuid, false);
+ moveElement(sourceUuid, targetUuid, false);
}
}
}
- confirmMove = (asChild: boolean) => {
- const sourceUuid = this.state.moveElements[0];
- const targetUuid = this.state.moveElements[1];
+ function confirmMove(asChild: boolean) {
+ const sourceUuid = moveElements[0];
+ const targetUuid = moveElements[1];
if (sourceUuid && targetUuid && sourceUuid !== targetUuid) {
- this.props.moveElement?.call(this, sourceUuid, targetUuid, asChild);
- this.setState({showMoveConfirmation: false, moveElements: [undefined, undefined]})
+ moveElement(sourceUuid, targetUuid, asChild);
+ cancelMove();
}
}
- cancelMove = () => {
- this.setState({showMoveConfirmation: false, moveElements: [undefined, undefined]})
+ function cancelMove() {
+ setShowMoveConfirmation(false);
+ setMoveElements([undefined, undefined]);
+ }
+
+ function isElementSelected(): boolean {
+ return selectedUuids.includes(props.step.uuid);
}
- isSelected = (): boolean => {
- return this.props.selectedUuid.includes(this.props.step.uuid);
+ function isElementHidden(): boolean {
+ return props.step.dslName === 'LogDefinition' && hideLogDSL;
}
- hasBorder = (): boolean => {
- return (this.props.step?.hasSteps() && !['FromDefinition'].includes(this.props.step.dslName))
+ function hasBorder(): boolean {
+ return (props.step?.hasSteps() && !['FromDefinition'].includes(props.step.dslName))
|| ['RouteConfigurationDefinition',
'RouteDefinition',
'TryDefinition',
'ChoiceDefinition',
- 'SwitchDefinition'].includes(this.props.step.dslName);
+ 'SwitchDefinition'].includes(props.step.dslName);
}
- isNotDraggable = (): boolean => {
- return ['FromDefinition', 'RouteConfigurationDefinition', 'RouteDefinition', 'WhenDefinition', 'OtherwiseDefinition'].includes(this.props.step.dslName);
+ function isNotDraggable(): boolean {
+ return ['FromDefinition', 'RouteConfigurationDefinition', 'RouteDefinition', 'WhenDefinition', 'OtherwiseDefinition'].includes(props.step.dslName);
}
- isWide = (): boolean => {
+ function isWide(): boolean {
return ['RouteConfigurationDefinition', 'RouteDefinition', 'ChoiceDefinition', 'SwitchDefinition', 'MulticastDefinition', 'TryDefinition', 'CircuitBreakerDefinition']
- .includes(this.props.step.dslName);
+ .includes(props.step.dslName);
}
- isAddStepButtonLeft = (): boolean => {
+ function isAddStepButtonLeft(): boolean {
return ['MulticastDefinition']
- .includes(this.props.step.dslName);
+ .includes(props.step.dslName);
}
- isHorizontal = (): boolean => {
- return ['MulticastDefinition'].includes(this.props.step.dslName);
+ function isHorizontal(): boolean {
+ return ['MulticastDefinition'].includes(props.step.dslName);
}
- isRoot = (): boolean => {
- return ['RouteConfigurationDefinition', 'RouteDefinition'].includes(this.props.step?.dslName);
+ function isRoot(): boolean {
+ return ['RouteConfigurationDefinition', 'RouteDefinition'].includes(props.step?.dslName);
}
- isInStepWithChildren = () => {
- const step: CamelElement = this.props.step;
+ function isInStepWithChildren() {
+ const step: CamelElement = props.step;
const children = CamelDefinitionApiExt.getElementChildrenDefinition(step.dslName);
- return children.filter((c: ChildElement) => c.name === 'steps' || c.multiple).length > 0 && this.props.inSteps;
+ return children.filter((c: ChildElement) => c.name === 'steps' || c.multiple).length > 0 && props.inSteps;
}
- getChildrenInfo = (step: CamelElement): [boolean, number, boolean, number, number] => {
+ 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
@@ -185,115 +171,134 @@ export class DslElement extends React.Component<Props, State> {
return [hasStepsField, stepsChildrenCount, hasNonStepsFields, nonStepChildrenCount, childrenCount]
}
- hasWideChildrenElement = () => {
- const [hasStepsField, stepsChildrenCount, hasNonStepsFields, nonStepChildrenCount, childrenCount] = this.getChildrenInfo(this.props.step);
- if (this.isHorizontal() && stepsChildrenCount > 1) return true;
+ 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;
}
- hasBorderOverSteps = (step: CamelElement) => {
- const [hasStepsField, stepsChildrenCount, hasNonStepsFields, nonStepChildrenCount] = this.getChildrenInfo(step);
+ function hasBorderOverSteps(step: CamelElement) {
+ const [hasStepsField, stepsChildrenCount, hasNonStepsFields, nonStepChildrenCount] = getChildrenInfo(step);
if (hasStepsField && stepsChildrenCount > 0 && hasNonStepsFields && nonStepChildrenCount > 0) return true;
else return false;
}
- getHeaderStyle = () => {
+ function getHeaderStyle() {
const style: CSSProperties = {
- width: this.isWide() ? "100%" : "",
- fontWeight: this.isSelected() ? "bold" : "normal",
+ width: isWide() ? "100%" : "",
+ fontWeight: isElementSelected() ? "bold" : "normal",
};
return style;
}
- sendPosition = (el: HTMLDivElement | null, isSelected: boolean) => {
- const node = ReactDOM.findDOMNode(this);
- if (node && el) {
- const header = Array.from(node.childNodes.values()).filter((n: any) => n.classList.contains("header"))[0];
- if (header) {
- const headerIcon: any = Array.from(header.childNodes.values()).filter((n: any) => n.classList.contains("header-icon"))[0];
- const headerRect = headerIcon.getBoundingClientRect();
- const rect = el.getBoundingClientRect();
- if (this.props.step.show){
- EventBus.sendPosition("add", this.props.step, this.props.parent, rect, headerRect, this.props.position, this.props.inSteps, isSelected);
- } else {
- EventBus.sendPosition("delete", this.props.step, this.props.parent, new DOMRect(), new DOMRect(), 0);
+ function sendPosition(el: HTMLDivElement | null) {
+ const isSelected = isElementSelected();
+ const isHidden = isElementHidden();
+ if (isHidden) {
+ EventBus.sendPosition("delete", props.step, props.parent, new DOMRect(), new DOMRect(), 0);
+ } else {
+ if (el) {
+ const header = Array.from(el.childNodes.values()).filter((n: any) => n.classList.contains("header"))[0];
+ if (header) {
+ const headerIcon: any = Array.from(header.childNodes.values()).filter((n: any) => n.classList.contains("header-icon"))[0];
+ const headerRect = headerIcon.getBoundingClientRect();
+ const rect = el.getBoundingClientRect();
+ if (props.step.showChildren) {
+ EventBus.sendPosition("add", props.step, props.parent, rect, headerRect, props.position, props.inSteps, isSelected);
+ } else {
+ EventBus.sendPosition("delete", props.step, props.parent, new DOMRect(), new DOMRect(), 0);
+ }
}
}
}
}
- getHeader = () => {
- const step: CamelElement = this.props.step;
- const parent = this.props.parent;
+ 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 availableModels = CamelUi.getSelectorModelsForParent(step.dslName, false);
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 = this.isSelected() ? headerClass + " selected" : headerClass;
+ const headerClasses = isElementSelected() ? headerClass + " selected" : headerClass;
return (
- <div className={headerClasses} style={this.getHeaderStyle()}>
- {!['RouteConfigurationDefinition', 'RouteDefinition'].includes(this.props.step.dslName) &&
- <div ref={el => this.sendPosition(el, this.isSelected())}
- className={"header-icon"}
- style={this.isWide() ? {width: ""} : {}}>
+ <div className={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={this.hasWideChildrenElement() ? "header-text" : ""}>
- {this.hasWideChildrenElement() && <div className="spacer"/>}
- {this.getHeaderTextWithTooltip(step)}
+ <div className={hasWideChildrenElement() ? "header-text" : ""}>
+ {hasWideChildrenElement() && <div className="spacer"/>}
+ {getHeaderTextWithTooltip(step)}
</div>
- {showInsertButton && this.getInsertElementButton()}
- {this.getDeleteButton()}
- {showAddButton && this.getAddElementButton()}
+ {showInsertButton && getInsertElementButton()}
+ {getDeleteButton()}
+ {showAddButton && getAddElementButton()}
</div>
)
}
- getHeaderTextWithTooltip = (step: CamelElement) => {
+ function getHeaderTextWithTooltip(step: CamelElement) {
const checkRequired = CamelUtil.checkRequired(step);
- const title = (step as any).description ? (step as any).description : CamelUi.getElementTitle(this.props.step);
- let className = this.hasWideChildrenElement() ? "text text-right" : "text text-bottom";
+ const title = (step as any).description ? (step as any).description : CamelUi.getElementTitle(props.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>
+ 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>))}>
+ content={checkRequired[1].map((text, i) => (<div key={i}>{text}</div>))}>
<Text className={className}>{title}</Text>
</Tooltip>
)
}
- getHeaderWithTooltip = (tooltip: string | undefined) => {
+ function getHeaderWithTooltip(tooltip: string | undefined) {
return (
- <Tooltip position={"left"}
- content={<div>{tooltip}</div>}>
- {this.getHeader()}
- </Tooltip>
+ <>
+ {getHeader()}
+ <Tooltip triggerRef={headerRef} position={"left"} content={<div>{tooltip}</div>}/>
+ </>
+
)
}
- getHeaderTooltip = (): string | undefined => {
- if (CamelUi.isShowExpressionTooltip(this.props.step)) return CamelUi.getExpressionTooltip(this.props.step);
- if (CamelUi.isShowUriTooltip(this.props.step)) return CamelUi.getUriTooltip(this.props.step);
+ 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;
}
- getElementHeader = () => {
- const tooltip = this.getHeaderTooltip();
- if (tooltip !== undefined && !this.state.isDragging) {
- return this.getHeaderWithTooltip(tooltip);
+ function getElementHeader() {
+ const tooltip = getHeaderTooltip();
+ if (tooltip !== undefined && !isDragging) {
+ return getHeaderWithTooltip(tooltip);
}
- return this.getHeader();
+ return getHeader();
}
- getChildrenStyle = () => {
+ function getChildrenStyle() {
const style: CSSProperties = {
display: "flex",
flexDirection: "row",
@@ -301,22 +306,22 @@ export class DslElement extends React.Component<Props, State> {
return style;
}
- getChildrenElementsStyle = (child: ChildElement, notOnlySteps: boolean) => {
- const step = this.props.step;
- const isBorder = child.name === 'steps' && this.hasBorderOverSteps(step);
+ 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",
- display: this.isHorizontal() || child.name !== 'steps' ? "flex" : "block",
+ display: isHorizontal() || child.name !== 'steps' ? "flex" : "block",
flexDirection: "row",
}
return style;
}
- getChildElements = () => {
- const step: CamelElement = this.props.step;
+ function getChildElements() {
+ const step: CamelElement = props.step;
let children: ChildElement[] = CamelDefinitionApiExt.getElementChildrenDefinition(step.dslName);
const notOnlySteps = children.filter(c => c.name === 'steps').length === 1
&& children.filter(c => c.multiple && c.name !== 'steps').length > 0;
@@ -331,163 +336,159 @@ export class DslElement extends React.Component<Props, State> {
children = children.filter(value => value.name !== 'onWhen')
}
return (
- <div key={step.uuid + "-children"} className="children" style={this.getChildrenStyle()}>
- {children.map((child: ChildElement, index: number) => this.getChildDslElements(child, index, notOnlySteps))}
+ <div key={step.uuid + "-children"} className="children" style={getChildrenStyle()}>
+ {children.map((child: ChildElement, index: number) => getChildDslElements(child, index, notOnlySteps))}
</div>
)
}
- getChildDslElements = (child: ChildElement, index: number, notOnlySteps: boolean) => {
- const step = this.props.step;
+ function getChildDslElements(child: ChildElement, index: number, notOnlySteps: boolean) {
+ const step = props.step;
const children: CamelElement[] = CamelDefinitionApiExt.getElementChildren(step, child);
if (children.length > 0) {
return (
- <div className={child.name + " has-child"} style={this.getChildrenElementsStyle(child, notOnlySteps)} key={step.uuid + "-child-" + index}>
+ <div className={child.name + " has-child"} style={getChildrenElementsStyle(child, notOnlySteps)}
+ key={step.uuid + "-child-" + index}>
{children.map((element, index) => (
<div key={step.uuid + child.className + index}>
<DslElement
- integration={this.props.integration}
- openSelector={this.props.openSelector}
- deleteElement={this.props.deleteElement}
- selectElement={this.props.selectElement}
- moveElement={this.props.moveElement}
- selectedUuid={this.props.selectedUuid}
inSteps={child.name === 'steps'}
position={index}
step={element}
parent={step}/>
</div>
))}
- {child.name === 'steps' && this.getAddStepButton()}
+ {child.name === 'steps' && getAddStepButton()}
</div>
)
} else if (child.name === 'steps') {
return (
- <div className={child.name + " has-child"} style={this.getChildrenElementsStyle(child, notOnlySteps)} key={step.uuid + "-child-" + index}>
- {this.getAddStepButton()}
+ <div className={child.name + " has-child"} style={getChildrenElementsStyle(child, notOnlySteps)}
+ key={step.uuid + "-child-" + index}>
+ {getAddStepButton()}
</div>
)
}
}
- getAddStepButton() {
- const {integration, step, selectedUuid} = this.props;
- const hideAddButton = step.dslName === 'StepDefinition' && !CamelDisplayUtil.isStepDefinitionExpanded(integration, step.uuid, selectedUuid.at(0));
+ function getAddStepButton() {
+ const {step} = props;
+ const hideAddButton = step.dslName === 'StepDefinition' && !CamelDisplayUtil.isStepDefinitionExpanded(integration, step.uuid, selectedUuids.at(0));
if (hideAddButton) return (<></>)
else return (
<Tooltip position={"bottom"}
content={<div>{"Add step to " + CamelUi.getTitle(step)}</div>}>
- <button type="button" aria-label="Add" onClick={e => this.openSelector(e)}
- className={this.isAddStepButtonLeft() ? "add-button add-button-left" : "add-button add-button-bottom"}>
- <AddIcon />
+ <button type="button" aria-label="Add" onClick={e => onOpenSelector(e)}
+ className={isAddStepButtonLeft() ? "add-button add-button-left" : "add-button add-button-bottom"}>
+ <AddIcon/>
</button>
</Tooltip>
)
}
- getAddElementButton() {
+ function getAddElementButton() {
return (
- <Tooltip position={"bottom"} content={<div>{"Add DSL element to " + CamelUi.getTitle(this.props.step)}</div>}>
+ <Tooltip position={"bottom"} content={<div>{"Add DSL element to " + CamelUi.getTitle(props.step)}</div>}>
<button
type="button"
aria-label="Add"
- onClick={e => this.openSelector(e, false)}
+ onClick={e => onOpenSelector(e, false)}
className={"add-element-button"}>
- <AddIcon />
+ <AddIcon/>
</button>
</Tooltip>
)
}
- getInsertElementButton() {
+ function getInsertElementButton() {
return (
<Tooltip position={"left"} content={<div>{"Insert element before"}</div>}>
- <button type="button" aria-label="Insert" onClick={e => this.openSelector(e, true, true)} className={"insert-element-button"}><InsertIcon />
+ <button type="button" aria-label="Insert" onClick={e => onOpenSelector(e, true, true)}
+ className={"insert-element-button"}><InsertIcon/>
</button>
</Tooltip>
)
}
- getDeleteButton() {
+ function getDeleteButton() {
return (
<Tooltip position={"right"} content={<div>{"Delete element"}</div>}>
- <button type="button" aria-label="Delete" onClick={e => this.delete(e)} className="delete-button"><DeleteIcon /></button>
+ <button type="button" aria-label="Delete" onClick={e => onDeleteElement(e)} className="delete-button">
+ <DeleteIcon/></button>
</Tooltip>
)
}
- getMoveConfirmation() {
+ function getMoveConfirmation() {
return (
<Modal
aria-label="title"
className='move-modal'
- isOpen={this.state.showMoveConfirmation}
+ isOpen={showMoveConfirmation}
variant={ModalVariant.small}
><Flex direction={{default: "column"}}>
<div>Select move type:</div>
- <Button key="place" variant="primary" onClick={event => this.confirmMove(false)}>Shift (target down)</Button>
- <Button key="child" variant="secondary" onClick={event => this.confirmMove(true)}>Move as target step</Button>
- <Button key="cancel" variant="tertiary" onClick={event => this.cancelMove()}>Cancel</Button>
+ <Button key="place" variant="primary" onClick={event => confirmMove(false)}>Shift (target down)</Button>
+ <Button key="child" variant="secondary" onClick={event => confirmMove(true)}>Move as target
+ step</Button>
+ <Button key="cancel" variant="tertiary" onClick={event => cancelMove()}>Cancel</Button>
</Flex>
</Modal>
)
}
- render() {
- const element: CamelElement = this.props.step;
- const className = "step-element" + (this.isSelected() ? " step-element-selected" : "")
- + (!this.props.step.show ? " hidden-step" : "");
- return (
- <div key={"root" + element.uuid}
- className={className}
- ref={el => this.sendPosition(el, this.isSelected())}
- style={{
- borderStyle: this.hasBorder() ? "dotted" : "none",
- borderColor: this.isSelected() ? "var(--step-border-color-selected)" : "var(--step-border-color)",
- marginTop: this.isInStepWithChildren() ? "16px" : "8px",
- zIndex: element.dslName === 'ToDefinition' ? 20 : 10,
- boxShadow: this.state.isDraggedOver ? "0px 0px 1px 2px var(--step-border-color-selected)" : "none",
- }}
- onMouseOver={event => event.stopPropagation()}
- onClick={event => this.selectElement(event)}
- onDragStart={event => {
- event.stopPropagation();
- event.dataTransfer.setData("text/plain", element.uuid);
- (event.target as any).style.opacity = .5;
- this.setState({isDragging: true});
- }}
- onDragEnd={event => {
- (event.target as any).style.opacity = '';
- this.setState({isDragging: false});
- }}
- onDragOver={event => {
- event.preventDefault();
- event.stopPropagation();
- if (element.dslName !== 'FromDefinition' && !this.state.isDragging) {
- this.setState({isDraggedOver: true});
- }
- }}
- onDragEnter={event => {
- event.preventDefault();
- event.stopPropagation();
- if (element.dslName !== 'FromDefinition') {
- this.setState({isDraggedOver: true});
- }
- }}
- onDragLeave={event => {
- event.preventDefault();
- event.stopPropagation();
- this.setState({isDraggedOver: false});
-
- }}
- onDrop={event => this.dragElement(event, element)}
- draggable={!this.isNotDraggable()}
- >
- {this.getElementHeader()}
- {this.getChildElements()}
- {this.getMoveConfirmation()}
- </div>
- )
- }
+ const element: CamelElement = props.step;
+ const className = "step-element" + (isElementSelected() ? " step-element-selected" : "") + (!props.step.showChildren ? " hidden-step" : "");
+ return (
+ <div key={"root" + element.uuid}
+ className={className}
+ ref={el => sendPosition(el)}
+ style={{
+ display: isElementHidden() ? "none" : "flex",
+ borderStyle: hasBorder() ? "dotted" : "none",
+ borderColor: isElementSelected() ? "var(--step-border-color-selected)" : "var(--step-border-color)",
+ marginTop: isInStepWithChildren() ? "16px" : "8px",
+ zIndex: element.dslName === 'ToDefinition' ? 20 : 10,
+ boxShadow: isDraggedOver ? "0px 0px 1px 2px var(--step-border-color-selected)" : "none",
+ }}
+ onMouseOver={event => event.stopPropagation()}
+ onClick={event => onSelectElement(event)}
+ onDragStart={event => {
+ event.stopPropagation();
+ event.dataTransfer.setData("text/plain", element.uuid);
+ (event.target as any).style.opacity = .5;
+ setIsDragging(true);
+ }}
+ onDragEnd={event => {
+ (event.target as any).style.opacity = '';
+ setIsDragging(false);
+ }}
+ onDragOver={event => {
+ event.preventDefault();
+ event.stopPropagation();
+ if (element.dslName !== 'FromDefinition' && !isDragging) {
+ setIsDraggedOver(true);
+ }
+ }}
+ onDragEnter={event => {
+ event.preventDefault();
+ event.stopPropagation();
+ if (element.dslName !== 'FromDefinition') {
+ setIsDraggedOver(true);
+ }
+ }}
+ onDragLeave={event => {
+ event.preventDefault();
+ event.stopPropagation();
+ setIsDraggedOver(false);
+ }}
+ onDrop={event => dragElement(event, element)}
+ draggable={!isNotDraggable()}
+ >
+ {getElementHeader()}
+ {getChildElements()}
+ {getMoveConfirmation()}
+ </div>
+ )
}
diff --git a/karavan-designer/src/designer/route/DslProperties.tsx b/karavan-designer/src/designer/route/DslProperties.tsx
index 87f93c79..337e12c0 100644
--- a/karavan-designer/src/designer/route/DslProperties.tsx
+++ b/karavan-designer/src/designer/route/DslProperties.tsx
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import React from 'react';
+import React, {useState} from 'react';
import {
Form,
Text,
@@ -25,101 +25,35 @@ import '../karavan.css';
import "@patternfly/patternfly/patternfly.css";
import {DataFormatField} from "./property/DataFormatField";
import {DslPropertyField} from "./property/DslPropertyField";
-import {
- ExpressionDefinition,
- DataFormatDefinition
-} from "karavan-core/lib/model/CamelDefinition";
-import {Integration, CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
-import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
-import {CamelUi, RouteToCreate} from "../utils/CamelUi";
+import {CamelUi} from "../utils/CamelUi";
import {CamelMetadataApi, DataFormats, PropertyMeta} from "karavan-core/lib/model/CamelMetadata";
-import {IntegrationHeader} from "../utils/KaravanComponents";
+import {IntegrationHeader} from "../utils/IntegrationHeader";
import CloneIcon from "@patternfly/react-icons/dist/esm/icons/clone-icon";
+import {useDesignerStore, useIntegrationStore} from "../KaravanStore";
+import {shallow} from "zustand/shallow";
+import {usePropertiesHook} from "./usePropertiesHook";
interface Props {
- integration: Integration,
- step?: CamelElement,
- onIntegrationUpdate?: any,
- onPropertyUpdate?: (element: CamelElement, newRoute?: RouteToCreate) => void
- onClone?: (element: CamelElement) => void
isRouteDesigner: boolean
- dark: boolean
-}
-
-interface State {
- step?: CamelElement,
- selectStatus: Map<string, boolean>
- isShowAdvanced: boolean
- isDescriptionExpanded?: boolean
}
-export class DslProperties extends React.Component<Props, State> {
-
- public state: State = {
- step: this.props.step,
- selectStatus: new Map<string, boolean>(),
- isShowAdvanced: false
- };
-
- propertyChanged = (fieldId: string, value: string | number | boolean | any, newRoute?: RouteToCreate) => {
- if (this.state.step) {
- const clone = CamelUtil.cloneStep(this.state.step);
- (clone as any)[fieldId] = value;
- this.setStep(clone)
- this.props.onPropertyUpdate?.call(this, clone, newRoute);
- }
- }
-
- dataFormatChanged = (value: DataFormatDefinition) => {
- value.uuid = this.state.step?.uuid ? this.state.step?.uuid : value.uuid;
- this.setStep(value);
- this.props.onPropertyUpdate?.call(this, value);
- }
-
- expressionChanged = (propertyName: string, exp: ExpressionDefinition) => {
- if (this.state.step) {
- const clone = (CamelUtil.cloneStep(this.state.step));
- (clone as any)[propertyName] = exp;
- this.setStep(clone);
- this.props.onPropertyUpdate?.call(this, clone);
- }
- }
+export function DslProperties(props: Props) {
- parametersChanged = (parameter: string, value: string | number | boolean | any, pathParameter?: boolean, newRoute?: RouteToCreate) => {
- if (this.state.step && this.state.step) {
- const clone = (CamelUtil.cloneStep(this.state.step));
- const parameters: any = {...(clone as any).parameters};
- parameters[parameter] = value;
- (clone as any).parameters = parameters;
- this.setStep(clone);
- this.props.onPropertyUpdate?.call(this, clone, newRoute);
- }
- }
+ const [integration, setIntegration] = useIntegrationStore((state) =>
+ [state.integration, state.setIntegration], shallow)
- cloneElement = () => {
- if (this.state.step) {
- this.props.onClone?.call(this, this.state.step);
- }
- }
+ const {cloneElement, onDataFormatChange, onPropertyChange, onParametersChange, onExpressionChange} = usePropertiesHook(props.isRouteDesigner);
- componentDidUpdate = (prevProps: Readonly<Props>, prevState: Readonly<State>, snapshot?: any) => {
- if (prevProps.step !== this.props.step) {
- this.setStep(this.props.step);
- }
- }
+ const [selectedStep, dark, setSelectedStep, setSelectedUuids] = useDesignerStore((s) =>
+ [s.selectedStep, s.dark, s.setSelectedStep, s.setSelectedUuids], shallow)
- setStep = (step?: CamelElement) => {
- this.setState({
- step: step,
- selectStatus: new Map<string, boolean>()
- });
- }
+ const [showAdvanced, setShowAdvanced] = useState<boolean>(false);
+ const [isDescriptionExpanded, setIsDescriptionExpanded] = useState<boolean>(false);
- getRouteHeader= (): JSX.Element => {
- const isDescriptionExpanded = this.state.isDescriptionExpanded;
- const title = this.state.step && CamelUi.getTitle(this.state.step)
- const description = this.state.step && CamelUi.getDescription(this.state.step);
+ function getRouteHeader(): JSX.Element {
+ const title = selectedStep && CamelUi.getTitle(selectedStep)
+ const description = selectedStep && CamelUi.getDescription(selectedStep);
const descriptionLines: string [] = description ? description?.split("\n") : [""];
return (
<div className="headers">
@@ -127,40 +61,42 @@ export class DslProperties extends React.Component<Props, State> {
<Title headingLevel="h1" size="md">{title}</Title>
</div>
<Text component={TextVariants.p}>{descriptionLines.at(0)}</Text>
- {descriptionLines.length > 1 && <ExpandableSection toggleText={isDescriptionExpanded ? 'Show less' : 'Show more'}
- onToggle={(_event, isExpanded) => this.setState({isDescriptionExpanded: !isDescriptionExpanded})}
- isExpanded={isDescriptionExpanded}>
- {descriptionLines.filter((value, index) => index > 0)
- .map((desc, index, array) => <Text key={index} component={TextVariants.p}>{desc}</Text>)}
- </ExpandableSection>}
+ {descriptionLines.length > 1 &&
+ <ExpandableSection toggleText={isDescriptionExpanded ? 'Show less' : 'Show more'}
+ onToggle={(_event, isExpanded) => setIsDescriptionExpanded(!isDescriptionExpanded)}
+ isExpanded={isDescriptionExpanded}>
+ {descriptionLines.filter((value, index) => index > 0)
+ .map((desc, index, array) => <Text key={index} component={TextVariants.p}>{desc}</Text>)}
+ </ExpandableSection>}
</div>
)
}
- getClonableElementHeader = (): JSX.Element => {
- const title = this.state.step && CamelUi.getTitle(this.state.step);
- const description = this.state.step?.dslName ? CamelMetadataApi.getCamelModelMetadataByClassName(this.state.step?.dslName)?.description : title;
+ function getClonableElementHeader(): JSX.Element {
+ const title = selectedStep && CamelUi.getTitle(selectedStep);
+ const description = selectedStep?.dslName ? CamelMetadataApi.getCamelModelMetadataByClassName(selectedStep?.dslName)?.description : title;
const descriptionLines: string [] = description ? description?.split("\n") : [""];
return (
<div className="headers">
<div className="top">
<Title headingLevel="h1" size="md">{title}</Title>
<Tooltip content="Clone element" position="bottom">
- <Button variant="link" onClick={() => this.cloneElement()} icon={<CloneIcon/>}/>
+ <Button variant="link" onClick={() => cloneElement()} icon={<CloneIcon/>}/>
</Tooltip>
</div>
- {descriptionLines.map((desc, index, array) => <Text key={index} component={TextVariants.p}>{desc}</Text>)}
+ {descriptionLines.map((desc, index, array) => <Text key={index}
+ component={TextVariants.p}>{desc}</Text>)}
</div>
)
}
- getComponentHeader = (): JSX.Element => {
- if (this.props.isRouteDesigner) return this.getRouteHeader()
- else return this.getClonableElementHeader();
+ function getComponentHeader(): JSX.Element {
+ if (props.isRouteDesigner) return getRouteHeader()
+ else return getClonableElementHeader();
}
- getProperties = (): PropertyMeta[] => {
- const dslName = this.state.step?.dslName;
+ function getProperties(): PropertyMeta[] {
+ const dslName = selectedStep?.dslName;
return CamelDefinitionApiExt.getElementProperties(dslName)
// .filter((p: PropertyMeta) => (showAdvanced && p.label.includes('advanced')) || (!showAdvanced && !p.label.includes('advanced')))
.filter((p: PropertyMeta) => !p.isObject || (p.isObject && !CamelUi.dslHasSteps(p.type)) || (dslName === 'CatchDefinition' && p.name === 'onWhen'))
@@ -168,58 +104,55 @@ export class DslProperties extends React.Component<Props, State> {
// .filter((p: PropertyMeta) => dslName && !(['RestDefinition', 'GetDefinition', 'PostDefinition', 'PutDefinition', 'PatchDefinition', 'DeleteDefinition', 'HeadDefinition'].includes(dslName) && ['param', 'responseMessage'].includes(p.name))) // TODO: configure this properties
}
- getPropertyFields = (properties: PropertyMeta[]) => {
+ function getPropertyFields(properties: PropertyMeta[]) {
return (<>
{properties.map((property: PropertyMeta) =>
<DslPropertyField key={property.name}
- integration={this.props.integration}
property={property}
- element={this.state.step}
- value={this.state.step ? (this.state.step as any)[property.name] : undefined}
- onExpressionChange={this.expressionChanged}
- onParameterChange={this.parametersChanged}
- onDataFormatChange={this.dataFormatChanged}
- onChange={this.propertyChanged}
- dark={this.props.dark}/>
+ element={selectedStep}
+ value={selectedStep ? (selectedStep as any)[property.name] : undefined}
+ onExpressionChange={onExpressionChange}
+ onParameterChange={onParametersChange}
+ onDataFormatChange={onDataFormatChange}
+ onPropertyChange={onPropertyChange}
+ />
)}
</>)
}
- render() {
- const dataFormats = DataFormats.map(value => value[0]);
- const dataFormatElement = this.state.step !== undefined && ['MarshalDefinition', 'UnmarshalDefinition'].includes(this.state.step.dslName);
- const properties = !dataFormatElement
- ? this.getProperties()
- : this.getProperties().filter(p => !dataFormats.includes(p.name));
- const propertiesMain = properties.filter(p => !p.label.includes("advanced"));
- const propertiesAdvanced = properties.filter(p => p.label.includes("advanced"));
- return (
- <div key={this.state.step ? this.state.step.uuid : 'integration'}
- className='properties'>
- <Form autoComplete="off" onSubmit={event => event.preventDefault()}>
- {this.state.step === undefined && <IntegrationHeader integration={this.props.integration}/>}
- {this.state.step && this.getComponentHeader()}
- {this.getPropertyFields(propertiesMain)}
- {this.state.step && !['MarshalDefinition', 'UnmarshalDefinition'].includes(this.state.step.dslName)
- && propertiesAdvanced.length > 0 &&
- <ExpandableSection
- toggleText={'Advanced properties'}
- onToggle={(_event, isExpanded) => this.setState({isShowAdvanced: !this.state.isShowAdvanced})}
- isExpanded={this.state.isShowAdvanced}>
- <div className="parameters">
- {this.getPropertyFields(propertiesAdvanced)}
- </div>
- </ExpandableSection>}
- {this.state.step && ['MarshalDefinition', 'UnmarshalDefinition'].includes(this.state.step.dslName) &&
- <DataFormatField
- integration={this.props.integration}
- dslName={this.state.step.dslName}
- value={this.state.step}
- onDataFormatChange={this.dataFormatChanged}
- dark={this.props.dark}/>
- }
- </Form>
- </div>
- )
- }
+ const dataFormats = DataFormats.map(value => value[0]);
+ const dataFormatElement = selectedStep !== undefined && ['MarshalDefinition', 'UnmarshalDefinition'].includes(selectedStep.dslName);
+ const properties = !dataFormatElement
+ ? getProperties()
+ : getProperties().filter(p => !dataFormats.includes(p.name));
+ const propertiesMain = properties.filter(p => !p.label.includes("advanced"));
+ const propertiesAdvanced = properties.filter(p => p.label.includes("advanced"));
+ return (
+ <div key={selectedStep ? selectedStep.uuid : 'integration'}
+ className='properties'>
+ <Form autoComplete="off" onSubmit={event => event.preventDefault()}>
+ {selectedStep === undefined && <IntegrationHeader/>}
+ {selectedStep && getComponentHeader()}
+ {getPropertyFields(propertiesMain)}
+ {selectedStep && !['MarshalDefinition', 'UnmarshalDefinition'].includes(selectedStep.dslName)
+ && propertiesAdvanced.length > 0 &&
+ <ExpandableSection
+ toggleText={'Advanced properties'}
+ onToggle={(_event, isExpanded) => setShowAdvanced(!showAdvanced)}
+ isExpanded={showAdvanced}>
+ <div className="parameters">
+ {getPropertyFields(propertiesAdvanced)}
+ </div>
+ </ExpandableSection>}
+ {selectedStep && ['MarshalDefinition', 'UnmarshalDefinition'].includes(selectedStep.dslName) &&
+ <DataFormatField
+ integration={integration}
+ dslName={selectedStep.dslName}
+ value={selectedStep}
+ onDataFormatChange={onDataFormatChange}
+ dark={dark}/>
+ }
+ </Form>
+ </div>
+ )
}
diff --git a/karavan-designer/src/designer/route/DslSelector.tsx b/karavan-designer/src/designer/route/DslSelector.tsx
index 47eb3b39..2e383cb6 100644
--- a/karavan-designer/src/designer/route/DslSelector.tsx
+++ b/karavan-designer/src/designer/route/DslSelector.tsx
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import React from 'react';
+import React, {useEffect, useState} from 'react';
import {
Badge,
Card, CardBody, CardFooter, CardHeader, Flex, FlexItem, Form, FormGroup, Gallery, Modal, PageSection,
@@ -24,66 +24,62 @@ import {
import '../karavan.css';
import {CamelUi} from "../utils/CamelUi";
import {DslMetaModel} from "../utils/DslMetaModel";
+import {useDesignerStore, useSelectorStore} from "../KaravanStore";
+import {shallow} from "zustand/shallow";
+import {useRouteDesignerHook} from "./useRouteDesignerHook";
interface Props {
- onDslSelect: (dsl: DslMetaModel, parentId: string, position?: number | undefined) => void,
- onClose?: () => void,
- parentId: string,
- parentDsl?: string,
- showSteps: boolean,
- dark: boolean,
- isOpen: boolean,
- position?: number
tabIndex?: string | number
}
-interface State {
- tabIndex: string | number
- filter: string;
- selectedLabels: string []
-}
+export function DslSelector (props: Props) {
-export class DslSelector extends React.Component<Props, State> {
+ const [showSelector, showSteps, parentId, parentDsl, selectorTabIndex, setShowSelector, setSelectorTabIndex,
+ selectedPosition, selectedLabels, addSelectedLabel, deleteSelectedLabel] =
+ useSelectorStore((s) =>
+ [s.showSelector, s.showSteps, s.parentId, s.parentDsl, s.selectorTabIndex, s.setShowSelector, s.setSelectorTabIndex,
+ s.selectedPosition, s.selectedLabels, s.addSelectedLabel, s.deleteSelectedLabel], shallow)
- public state: State = {
- tabIndex: this.props.tabIndex ? this.props.tabIndex : (this.props.parentDsl ? 'eip' : 'kamelet'),
- filter: '',
- selectedLabels: []
- }
- selectTab = (evt: React.MouseEvent<HTMLElement, MouseEvent>, eventKey: string | number) => {
- this.setState({tabIndex: eventKey});
- }
+ const [dark] = useDesignerStore((s) => [s.dark], shallow)
- componentDidUpdate = (prevProps: Readonly<Props>, prevState: Readonly<State>, snapshot?: any) => {
- if (prevProps.parentDsl !== this.props.parentDsl) {
- this.setState({tabIndex: CamelUi.getSelectorModelTypes(this.props.parentDsl, this.props.showSteps)[0][0]});
- }
+ const {onDslSelect} = useRouteDesignerHook();
+
+
+ const [filter, setFilter] = useState<string>('');
+
+ useEffect(() => {
+ }, [selectedLabels]);
+
+
+ function selectTab(evt: React.MouseEvent<HTMLElement, MouseEvent>, eventKey: string | number) {
+ setSelectorTabIndex(eventKey);
}
- selectDsl = (evt: React.MouseEvent, dsl: any) => {
+ function selectDsl(evt: React.MouseEvent, dsl: any) {
evt.stopPropagation();
- this.setState({filter: ""});
- this.props.onDslSelect.call(this, dsl, this.props.parentId, this.props.position);
+ setFilter('');
+ setShowSelector(false);
+ onDslSelect(dsl, parentId, selectedPosition);
}
- searchInput = () => {
+ function searchInput() {
return (
<Form isHorizontal className="search" autoComplete="off">
<FormGroup fieldId="search">
- <TextInput className="text-field" type="text" id="search" name="search"
- value={this.state.filter}
- onChange={(_, value) => this.setState({filter: value})}/>
+ <TextInput className="text-field" type="text" id="search" name="search"
+ value={filter}
+ onChange={(_, value) => setFilter(value)}/>
</FormGroup>
</Form>
)
}
- getCard(dsl: DslMetaModel, index: number) {
+ function getCard(dsl: DslMetaModel, index: number) {
const labels = dsl.labels !== undefined ? dsl.labels.split(",").filter(label => label !== 'eip') : [];
return (
<Card key={dsl.dsl + index} isCompact className="dsl-card"
- onClick={event => this.selectDsl(event, dsl)}>
+ onClick={event => selectDsl(event, dsl)}>
<CardHeader className="header-labels">
<Badge isRead className="support-level labels">{dsl.supportLevel}</Badge>
{['kamelet', 'component'].includes(dsl.navigation.toLowerCase()) &&
@@ -99,7 +95,8 @@ export class DslSelector extends React.Component<Props, State> {
</CardBody>
<CardFooter className="footer-labels">
<div style={{display: "flex", flexDirection: "row", justifyContent: "start"}}>
- {labels.map(label => <Badge key={label} isRead className="labels">{label}</Badge>)}
+ {labels.map((label, index) => <Badge key={label + "-" + index} isRead
+ className="labels">{label}</Badge>)}
</div>
</CardFooter>
@@ -107,90 +104,80 @@ export class DslSelector extends React.Component<Props, State> {
)
}
- close = () => {
- this.setState({filter: ""});
- this.props.onClose?.call(this);
+ function close() {
+ setFilter('');
+ setShowSelector(false);
}
- selectLabel = (eipLabel: string) => {
- if (!this.state.selectedLabels.includes(eipLabel)) {
- this.setState((state) => {
- state.selectedLabels.push(eipLabel);
- return state
- })
+ function selectLabel(eipLabel: string) {
+ if (!selectedLabels.includes(eipLabel)) {
+ addSelectedLabel(eipLabel);
} else {
- this.setState((state) => {
- const index = state.selectedLabels.findIndex((label) => label === eipLabel);
- state.selectedLabels.splice(index, 1);
- return state;
- })
+ deleteSelectedLabel(eipLabel);
}
}
- render() {
- const isEip = this.state.tabIndex === 'eip';
- const {parentDsl, isOpen} = this.props;
- const title = parentDsl === undefined ? "Select source" : "Select step";
- const navigation: string = this.state.tabIndex ? this.state.tabIndex.toString() : "";
- const elements = CamelUi.getSelectorModelsForParentFiltered(parentDsl, navigation, this.props.showSteps);
- const eipLabels = [...new Set(elements.map(e => e.labels).join(",").split(",").filter(e => e !== 'eip'))];
- const filteredElement = elements
- .filter((dsl: DslMetaModel) => CamelUi.checkFilter(dsl, this.state.filter))
- .filter((dsl: DslMetaModel) => {
- if (!isEip || this.state.selectedLabels.length === 0) {
- return true;
- } else {
- return dsl.labels.split(",").some(r => this.state.selectedLabels.includes(r));
- }
- });
+ const isEip = selectorTabIndex === 'eip';
+ const title = parentDsl === undefined ? "Select source" : "Select step";
+ const navigation: string = selectorTabIndex ? selectorTabIndex.toString() : '';
+ const elements = CamelUi.getSelectorModelsForParentFiltered(parentDsl, navigation, showSteps);
+ const eipLabels = [...new Set(elements.map(e => e.labels).join(",").split(",").filter(e => e !== 'eip'))];
+ const filteredElement = elements
+ .filter((dsl: DslMetaModel) => CamelUi.checkFilter(dsl, filter))
+ .filter((dsl: DslMetaModel) => {
+ if (!isEip || selectedLabels.length === 0) {
+ return true;
+ } else {
+ return dsl.labels.split(",").some(r => selectedLabels.includes(r));
+ }
+ });
- return (
- <Modal
- aria-label={title}
- width={'90%'}
- className='dsl-modal'
- isOpen={this.props.isOpen}
- onClose={() => this.close()}
- header={
- <Flex direction={{default: "column"}}>
- <FlexItem>
- <h3>{title}</h3>
- {this.searchInput()}
- </FlexItem>
- <FlexItem>
- <Tabs style={{overflow: 'hidden'}} activeKey={this.state.tabIndex}
- onSelect={this.selectTab}>
- {parentDsl !== undefined &&
- <Tab eventKey={"eip"} key={"tab-eip"}
- title={<TabTitleText>Integration Patterns</TabTitleText>}>
- </Tab>
- }
- <Tab eventKey={'kamelet'} key={"tab-kamelet"}
- title={<TabTitleText>Kamelets</TabTitleText>}>
+ return (
+ <Modal
+ aria-label={title}
+ width={'90%'}
+ className='dsl-modal'
+ isOpen={showSelector}
+ onClose={() => close()}
+ header={
+ <Flex direction={{default: "column"}}>
+ <FlexItem>
+ <h3>{title}</h3>
+ {searchInput()}
+ </FlexItem>
+ <FlexItem>
+ <Tabs style={{overflow: 'hidden'}} activeKey={selectorTabIndex}
+ onSelect={selectTab}>
+ {parentDsl !== undefined &&
+ <Tab eventKey={"eip"} key={"tab-eip"}
+ title={<TabTitleText>Integration Patterns</TabTitleText>}>
</Tab>
- <Tab eventKey={'component'} key={'tab-component'}
- title={<TabTitleText>Components</TabTitleText>}>
- </Tab>
- </Tabs>
- </FlexItem>
- </Flex>
- }
- actions={{}}>
- <PageSection padding={{default:"noPadding"}} variant={this.props.dark ? "darker" : "light"}>
- {isEip && <ToggleGroup aria-label="Labels" isCompact>
- {eipLabels.map(eipLabel => <ToggleGroupItem
- key={eipLabel}
- text={eipLabel}
- buttonId={eipLabel}
- isSelected={this.state.selectedLabels.includes(eipLabel)}
- onChange={selected => this.selectLabel(eipLabel)}
- />)}
- </ToggleGroup>}
- <Gallery key={"gallery-" + navigation} hasGutter className="dsl-gallery">
- {isOpen && filteredElement.map((dsl: DslMetaModel, index: number) => this.getCard(dsl, index))}
- </Gallery>
- </PageSection>
- </Modal>
- )
- }
+ }
+ <Tab eventKey={'kamelet'} key={"tab-kamelet"}
+ title={<TabTitleText>Kamelets</TabTitleText>}>
+ </Tab>
+ <Tab eventKey={'component'} key={'tab-component'}
+ title={<TabTitleText>Components</TabTitleText>}>
+ </Tab>
+ </Tabs>
+ </FlexItem>
+ </Flex>
+ }
+ actions={{}}>
+ <PageSection padding={{default: "noPadding"}} variant={dark ? "darker" : "light"}>
+ {isEip && <ToggleGroup aria-label="Labels" isCompact>
+ {eipLabels.map(eipLabel => <ToggleGroupItem
+ key={eipLabel}
+ text={eipLabel}
+ buttonId={eipLabel}
+ isSelected={selectedLabels.includes(eipLabel)}
+ onChange={selected => selectLabel(eipLabel)}
+ />)}
+ </ToggleGroup>}
+ <Gallery key={"gallery-" + navigation} hasGutter className="dsl-gallery">
+ {showSelector && filteredElement.map((dsl: DslMetaModel, index: number) => getCard(dsl, index))}
+ </Gallery>
+ </PageSection>
+ </Modal>
+ )
}
\ No newline at end of file
diff --git a/karavan-designer/src/designer/route/RouteDesigner.tsx b/karavan-designer/src/designer/route/RouteDesigner.tsx
index f7607b90..fcf2d6fe 100644
--- a/karavan-designer/src/designer/route/RouteDesigner.tsx
+++ b/karavan-designer/src/designer/route/RouteDesigner.tsx
@@ -14,182 +14,109 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import React from 'react';
+import React, {useCallback, useEffect, useRef} from 'react';
import {
Drawer,
DrawerPanelContent,
DrawerContent,
DrawerContentBody,
- Button, Modal,
- PageSection,
+ Button
} from '@patternfly/react-core';
import '../karavan.css';
import {DslSelector} from "./DslSelector";
import {DslProperties} from "./DslProperties";
-import {CamelElement, Integration} from "karavan-core/lib/model/IntegrationDefinition";
import {DslConnections} from "./DslConnections";
import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon";
import {DslElement} from "./DslElement";
import {CamelUi} from "../utils/CamelUi";
-import {CamelDisplayUtil} from "karavan-core/lib/api/CamelDisplayUtil";
-import {RouteDesignerLogic} from "./RouteDesignerLogic";
+import {useRouteDesignerHook} from "./useRouteDesignerHook";
+import {useConnectionsStore, useDesignerStore, useIntegrationStore, useSelectorStore} from "../KaravanStore";
+import {shallow} from "zustand/shallow";
+import useResizeObserver from "./useResizeObserver";
+import {Command, EventBus} from "../utils/EventBus";
+import useMutationsObserver from "./useDrawerMutationsObserver";
+import {DeleteConfirmation} from "./DeleteConfirmation";
-interface Props {
- onSave?: (integration: Integration, propertyOnly: boolean) => void
- integration: Integration
- dark: boolean
-}
+export function RouteDesigner() {
-export interface RouteDesignerState {
- logic: RouteDesignerLogic
- integration: Integration
- selectedStep?: CamelElement
- showSelector: boolean
- showDeleteConfirmation: boolean
- deleteMessage: string
- parentId: string
- parentDsl?: string
- selectedPosition?: number
- showSteps: boolean
- selectedUuids: string []
- key: string
- width: number
- height: number
- top: number
- left: number
- clipboardSteps: CamelElement[]
- shiftKeyPressed?: boolean
- ref?: any
- printerRef?: any
- propertyOnly: boolean
- selectorTabIndex?: string | number
-}
+ const {openSelector, createRouteConfiguration, onCommand, handleKeyDown, handleKeyUp, unselectElement} = useRouteDesignerHook();
-export class RouteDesigner extends React.Component<Props, RouteDesignerState> {
+ const [integration] = useIntegrationStore((state) => [state.integration], shallow)
+ const [showDeleteConfirmation, setPosition, width, height, top, left, hideLogDSL] = useDesignerStore((s) =>
+ [s.showDeleteConfirmation, s.setPosition, s.width, s.height, s.top, s.left, s.hideLogDSL], shallow)
- public state: RouteDesignerState = {
- logic: new RouteDesignerLogic(this),
- integration: CamelDisplayUtil.setIntegrationVisibility(this.props.integration, undefined),
- showSelector: false,
- showDeleteConfirmation: false,
- deleteMessage: '',
- parentId: '',
- showSteps: true,
- selectedUuids: [],
- clipboardSteps: [],
- key: "",
- width: 1000,
- height: 1000,
- top: 0,
- left: 0,
- ref: React.createRef(),
- printerRef: React.createRef(),
- propertyOnly: false,
- };
+ const [showSelector] = useSelectorStore((s) => [s.showSelector], shallow)
- componentDidMount() {
- this.state.logic.componentDidMount();
- }
-
- componentWillUnmount() {
- this.state.logic.componentWillUnmount();
- }
-
- handleResize = (event: any) => {
- return this.state.logic.handleResize(event);
- }
-
- handleKeyDown = (event: KeyboardEvent) => {
- return this.state.logic.handleKeyDown(event);
- }
+ const [clearSteps] = useConnectionsStore((s) => [s.clearSteps], shallow)
- handleKeyUp = (event: KeyboardEvent) => {
- return this.state.logic.handleKeyUp(event);
- }
+ const onChangeGraphSize = useCallback((target: HTMLDivElement) => {
+ changeGraphSize();
+ }, [])
- componentDidUpdate = (prevProps: Readonly<Props>, prevState: Readonly<RouteDesignerState>, snapshot?: any) => {
- return this.state.logic.componentDidUpdate(prevState, snapshot);
+ function changeGraphSize () {
+ if (flowRef && flowRef.current) {
+ const el = flowRef.current;
+ const rect = el.getBoundingClientRect();
+ setPosition(rect.width, rect.height, rect.top, rect.left)
+ }
}
- getSelectorModal() {
- return (
- <DslSelector
- isOpen={this.state.showSelector}
- onClose={() => this.state.logic.closeDslSelector()}
- dark={this.props.dark}
- parentId={this.state.parentId}
- parentDsl={this.state.parentDsl}
- showSteps={this.state.showSteps}
- position={this.state.selectedPosition}
- tabIndex={this.state.selectorTabIndex}
- onDslSelect={this.state.logic.onDslSelect}/>)
- }
+ const firstRef = useResizeObserver(onChangeGraphSize);
+ const secondRef = useMutationsObserver(onChangeGraphSize);
+ const printerRef = useRef<HTMLDivElement | null>(null);
+ const flowRef = useRef<HTMLDivElement | null>(null);
- getDeleteConfirmation() {
- let htmlContent: string = this.state.deleteMessage;
- return (<Modal
- className="modal-delete"
- title="Confirmation"
- isOpen={this.state.showDeleteConfirmation}
- onClose={() => this.setState({showDeleteConfirmation: false})}
- actions={[
- <Button key="confirm" variant="primary" onClick={e => this.state.logic.deleteElement()}>Delete</Button>,
- <Button key="cancel" variant="link"
- onClick={e => this.setState({showDeleteConfirmation: false})}>Cancel</Button>
- ]}
- onEscapePress={e => this.setState({showDeleteConfirmation: false})}>
- <div>
- {htmlContent}
- </div>
- </Modal>)
- }
+ useEffect(()=> {
+ // window.addEventListener('resize', changeGraphSize);
+ const interval = setInterval(() => {
+ changeGraphSize();
+ }, 500);
+ window.addEventListener('keydown', handleKeyDown);
+ window.addEventListener('keyup', handleKeyUp);
+ const commandSub = EventBus.onCommand()?.subscribe((command: Command) => onCommand(command, printerRef));
+ if (flowRef.current === null) {
+ clearSteps();
+ } else {
+ changeGraphSize();
+ }
+ return ()=> {
+ clearInterval(interval)
+ // window.removeEventListener('resize', changeGraphSize);
+ window.removeEventListener('keydown', handleKeyDown);
+ window.removeEventListener('keyup', handleKeyUp);
+ commandSub?.unsubscribe();
+ }
+ }, [showSelector, integration])
- getPropertiesPanel() {
+ function getPropertiesPanel() {
return (
- <DrawerPanelContent onResize={(_event, width) => this.setState({key: Math.random().toString()})}
- style={{transform: "initial"}} isResizable hasNoBorder defaultSize={'400px'}
+ <DrawerPanelContent style={{transform: "initial"}} isResizable hasNoBorder defaultSize={'400px'}
maxSize={'800px'} minSize={'300px'}>
- <DslProperties ref={this.state.ref}
- integration={this.state.integration}
- step={this.state.selectedStep}
- onIntegrationUpdate={this.state.logic.onIntegrationUpdate}
- onPropertyUpdate={this.state.logic.onPropertyUpdate}
- isRouteDesigner={true}
- dark={this.props.dark}/>
+ <DslProperties isRouteDesigner={true}/>
</DrawerPanelContent>
)
}
- getGraph() {
- const {selectedUuids, integration, key, width, height, top, left} = this.state;
+ function getGraph() {
const routes = CamelUi.getRoutes(integration);
const routeConfigurations = CamelUi.getRouteConfigurations(integration);
return (
- <div ref={this.state.printerRef} className="graph">
- <DslConnections height={height} width={width} top={top} left={left} integration={integration}/>
- <div className="flows" data-click="FLOWS" onClick={event => this.state.logic.unselectElement(event)}
- ref={el => this.state.logic.onResizePage(el)}>
+ <div className="graph" ref={printerRef}>
+ <DslConnections/>
+ <div id="flows"
+ className="flows"
+ data-click="FLOWS"
+ onClick={event => {unselectElement(event)}}
+ ref={flowRef}>
{routeConfigurations?.map((routeConfiguration, index: number) => (
- <DslElement key={routeConfiguration.uuid + key}
- integration={integration}
- openSelector={this.state.logic.openSelector}
- deleteElement={this.state.logic.showDeleteConfirmation}
- selectElement={this.state.logic.selectElement}
- moveElement={this.state.logic.moveElement}
- selectedUuid={selectedUuids}
+ <DslElement key={routeConfiguration.uuid}
inSteps={false}
position={index}
step={routeConfiguration}
parent={undefined}/>
))}
{routes?.map((route: any, index: number) => (
- <DslElement key={route.uuid + key}
- integration={integration}
- openSelector={this.state.logic.openSelector}
- deleteElement={this.state.logic.showDeleteConfirmation}
- selectElement={this.state.logic.selectElement}
- moveElement={this.state.logic.moveElement}
- selectedUuid={selectedUuids}
+ <DslElement key={route.uuid}
inSteps={false}
position={index}
step={route}
@@ -199,31 +126,36 @@ export class RouteDesigner extends React.Component<Props, RouteDesignerState> {
<Button
variant={routes.length === 0 ? "primary" : "secondary"}
icon={<PlusIcon/>}
- onClick={e => this.state.logic.openSelector(undefined, undefined)}>Create route
+ onClick={e => {
+ openSelector(undefined, undefined)
+ }}
+ >
+ Create route
</Button>
<Button
variant="secondary"
icon={<PlusIcon/>}
- onClick={e => this.state.logic.createRouteConfiguration()}>Create configuration
+ onClick={e => createRouteConfiguration()}
+ >
+ Create configuration
</Button>
</div>
</div>
</div>)
}
- render() {
- return (
- <PageSection className="dsl-page" isFilled padding={{default: 'noPadding'}}>
- <div className="dsl-page-columns">
- <Drawer isExpanded isInline>
- <DrawerContent panelContent={this.getPropertiesPanel()}>
- <DrawerContentBody>{this.getGraph()}</DrawerContentBody>
- </DrawerContent>
- </Drawer>
- </div>
- {this.state.showSelector === true && this.getSelectorModal()}
- {this.getDeleteConfirmation()}
- </PageSection>
- );
- }
+ const hasFlows = integration?.spec?.flows?.length && integration?.spec?.flows?.length > 0;
+ return (
+ <div className="dsl-page" ref={firstRef}>
+ <div className="dsl-page-columns" ref={secondRef}>
+ <Drawer isExpanded isInline>
+ <DrawerContent panelContent={getPropertiesPanel()}>
+ <DrawerContentBody>{hasFlows && getGraph()}</DrawerContentBody>
+ </DrawerContent>
+ </Drawer>
+ </div>
+ {showSelector && <DslSelector/>}
+ {showDeleteConfirmation && <DeleteConfirmation/>}
+ </div>
+ )
}
\ No newline at end of file
diff --git a/karavan-designer/src/designer/route/RouteDesignerLogic.tsx b/karavan-designer/src/designer/route/RouteDesignerLogic.tsx
deleted file mode 100644
index b352a056..00000000
--- a/karavan-designer/src/designer/route/RouteDesignerLogic.tsx
+++ /dev/null
@@ -1,396 +0,0 @@
-/*
- * 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 from 'react';
-import '../karavan.css';
-import {DslMetaModel} from "../utils/DslMetaModel";
-import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
-import {ChoiceDefinition, FromDefinition, LogDefinition, RouteConfigurationDefinition, RouteDefinition} from "karavan-core/lib/model/CamelDefinition";
-import {CamelElement, Integration} from "karavan-core/lib/model/IntegrationDefinition";
-import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
-import {CamelDefinitionApi} from "karavan-core/lib/api/CamelDefinitionApi";
-import {Command, EventBus} from "../utils/EventBus";
-import {RouteToCreate} from "../utils/CamelUi";
-import {CamelDisplayUtil} from "karavan-core/lib/api/CamelDisplayUtil";
-import {toPng} from 'html-to-image';
-import {RouteDesigner, RouteDesignerState} from "./RouteDesigner";
-import {findDOMNode} from "react-dom";
-import {Subscription} from "rxjs";
-import debounce from 'lodash.debounce';
-
-export class RouteDesignerLogic {
-
- routeDesigner: RouteDesigner
- commandSub?: Subscription
-
- constructor(routeDesigner: RouteDesigner) {
- this.routeDesigner = routeDesigner;
- }
-
- componentDidMount() {
- window.addEventListener('resize', this.routeDesigner.handleResize);
- window.addEventListener('keydown', this.routeDesigner.handleKeyDown);
- window.addEventListener('keyup', this.routeDesigner.handleKeyUp);
- const element = findDOMNode(this.routeDesigner.state.ref.current)?.parentElement?.parentElement;
- const checkResize = (mutations: any) => {
- const el = mutations[0].target;
- const w = el.clientWidth;
- const isChange = mutations.map((m: any) => `${m.oldValue}`).some((prev: any) => prev.indexOf(`width: ${w}px`) === -1);
- if (isChange) this.routeDesigner.setState({key: Math.random().toString()});
- }
- if (element) {
- const observer = new MutationObserver(checkResize);
- observer.observe(element, {attributes: true, attributeOldValue: true, attributeFilter: ['style']});
- }
- this.commandSub = EventBus.onCommand()?.subscribe((command: Command) => this.onCommand(command));
- }
-
- componentWillUnmount() {
- window.removeEventListener('resize', this.routeDesigner.handleResize);
- window.removeEventListener('keydown', this.routeDesigner.handleKeyDown);
- window.removeEventListener('keyup', this.routeDesigner.handleKeyUp);
- this.commandSub?.unsubscribe();
- }
-
- handleResize = (event: any) => {
- this.routeDesigner.setState({key: Math.random().toString()});
- }
-
- handleKeyDown = (event: KeyboardEvent) => {
- if ((event.shiftKey)) {
- this.routeDesigner.setState({shiftKeyPressed: true});
- }
- if (window.document.hasFocus() && window.document.activeElement) {
- if (['BODY', 'MAIN'].includes(window.document.activeElement.tagName)) {
- let charCode = String.fromCharCode(event.which).toLowerCase();
- if ((event.ctrlKey || event.metaKey) && charCode === 'c') {
- this.copyToClipboard();
- } else if ((event.ctrlKey || event.metaKey) && charCode === 'v') {
- this.pasteFromClipboard();
- }
- }
- } else {
- if (event.repeat) {
- window.dispatchEvent(event);
- }
- }
- }
-
- handleKeyUp = (event: KeyboardEvent) => {
- this.routeDesigner.setState({shiftKeyPressed: false});
- if (event.repeat) {
- window.dispatchEvent(event);
- }
- }
-
- componentDidUpdate = (prevState: Readonly<RouteDesignerState>, snapshot?: any) => {
- if (prevState.key !== this.routeDesigner.state.key) {
- this.routeDesigner.props.onSave?.call(this, this.routeDesigner.state.integration, this.routeDesigner.state.propertyOnly);
- }
- }
-
- copyToClipboard = (): void => {
- const {integration, selectedUuids} = this.routeDesigner.state;
- const steps: CamelElement[] = []
- selectedUuids.forEach(selectedUuid => {
- const selectedElement = CamelDefinitionApiExt.findElementInIntegration(integration, selectedUuid);
- if (selectedElement) {
- steps.push(selectedElement);
- }
- })
- if (steps.length >0) {
- this.routeDesigner.setState(prevState => ({
- key: Math.random().toString(),
- clipboardSteps: [...steps]
- }));
- }
- }
- pasteFromClipboard = (): void => {
- const {integration, selectedUuids, clipboardSteps} = this.routeDesigner.state;
- if (clipboardSteps.length === 1 && clipboardSteps[0]?.dslName === 'FromDefinition') {
- const clone = CamelUtil.cloneStep(clipboardSteps[0], true);
- const route = CamelDefinitionApi.createRouteDefinition({from: clone});
- this.addStep(route, '', 0)
- } else if (clipboardSteps.length === 1 && clipboardSteps[0]?.dslName === 'RouteDefinition') {
- const clone = CamelUtil.cloneStep(clipboardSteps[0], true);
- this.addStep(clone, '', 0)
- } else if (selectedUuids.length === 1) {
- const targetMeta = CamelDefinitionApiExt.findElementMetaInIntegration(integration, selectedUuids[0]);
- clipboardSteps.reverse().forEach(clipboardStep => {
- if (clipboardStep && targetMeta.parentUuid) {
- const clone = CamelUtil.cloneStep(clipboardStep, true);
- this.addStep(clone, targetMeta.parentUuid, targetMeta.position);
- }
- })
- }
- }
-
- onCommand = (command: Command) => {
- switch (command.command){
- case "downloadImage": this.integrationImageDownload()
- }
- }
-
- onPropertyUpdate = debounce((element: CamelElement, newRoute?: RouteToCreate) => {
- if (newRoute) {
- let i = CamelDefinitionApiExt.updateIntegrationRouteElement(this.routeDesigner.state.integration, element);
- const f = CamelDefinitionApi.createFromDefinition({uri: newRoute.componentName, parameters: {name: newRoute.name}});
- const r = CamelDefinitionApi.createRouteDefinition({from: f, id: newRoute.name})
- i = CamelDefinitionApiExt.addStepToIntegration(i, r, '');
- const clone = CamelUtil.cloneIntegration(i);
- this.routeDesigner.setState(prevState => ({
- integration: clone,
- key: Math.random().toString(),
- showSelector: false,
- selectedStep: element,
- propertyOnly: false,
- selectedUuids: [element.uuid]
- }));
- } else {
- const clone = CamelUtil.cloneIntegration(this.routeDesigner.state.integration);
- const i = CamelDefinitionApiExt.updateIntegrationRouteElement(clone, element);
- this.routeDesigner.setState({integration: i, propertyOnly: true, key: Math.random().toString()});
- }
- }, 300, {leading: true})
-
- showDeleteConfirmation = (id: string) => {
- let message: string;
- const uuidsToDelete:string [] = [id];
- let ce: CamelElement;
- ce = CamelDefinitionApiExt.findElementInIntegration(this.routeDesigner.state.integration, id)!;
- if (ce.dslName === 'FromDefinition') { // Get the RouteDefinition for this.routeDesigner. Use its uuid.
- let flows = this.routeDesigner.state.integration.spec.flows!;
- for (let i = 0; i < flows.length; i++) {
- if (flows[i].dslName === 'RouteDefinition') {
- let routeDefinition: RouteDefinition = flows[i];
- if (routeDefinition.from.uuid === id) {
- uuidsToDelete.push(routeDefinition.uuid);
- break;
- }
- }
- }
- message = 'Deleting the first element will delete the entire route!';
- } else if (ce.dslName === 'RouteDefinition') {
- message = 'Delete route?';
- } else if (ce.dslName === 'RouteConfigurationDefinition') {
- message = 'Delete route configuration?';
- } else {
- message = 'Delete element from route?';
- }
- this.routeDesigner.setState(prevState => ({
- showSelector: false,
- showDeleteConfirmation: true,
- deleteMessage: message,
- selectedUuids: uuidsToDelete,
- }));
- }
-
- deleteElement = () => {
- this.routeDesigner.state.selectedUuids.forEach(uuidToDelete => {
- const i = CamelDefinitionApiExt.deleteStepFromIntegration(this.routeDesigner.state.integration, uuidToDelete);
- this.routeDesigner.setState(prevState => ({
- integration: i,
- showSelector: false,
- showDeleteConfirmation: false,
- deleteMessage: '',
- key: Math.random().toString(),
- selectedStep: undefined,
- propertyOnly: false,
- selectedUuids: [uuidToDelete],
- }));
- const el = new CamelElement("");
- el.uuid = uuidToDelete;
- EventBus.sendPosition("delete", el, undefined, new DOMRect(), new DOMRect(), 0);
- });
- }
-
- selectElement = (element: CamelElement) => {
- const {shiftKeyPressed, selectedUuids, integration} = this.routeDesigner.state;
- let canNotAdd: boolean = false;
- if (shiftKeyPressed) {
- const hasFrom = selectedUuids.map(e => CamelDefinitionApiExt.findElementInIntegration(integration, e)?.dslName === 'FromDefinition').filter(r => r).length > 0;
- canNotAdd = hasFrom || (selectedUuids.length > 0 && element.dslName === 'FromDefinition');
- }
- const add = shiftKeyPressed && !selectedUuids.includes(element.uuid);
- const remove = shiftKeyPressed && selectedUuids.includes(element.uuid);
- const i = CamelDisplayUtil.setIntegrationVisibility(this.routeDesigner.state.integration, element.uuid);
- this.routeDesigner.setState((prevState: RouteDesignerState) => {
- if (remove) {
- const index = prevState.selectedUuids.indexOf(element.uuid);
- prevState.selectedUuids.splice(index, 1);
- } else if (add && !canNotAdd) {
- prevState.selectedUuids.push(element.uuid);
- }
- const uuid: string = prevState.selectedUuids.includes(element.uuid) ? element.uuid : prevState.selectedUuids.at(0) || '';
- const selectedElement = shiftKeyPressed ? CamelDefinitionApiExt.findElementInIntegration(integration, uuid) : element;
- return {
- integration: i,
- selectedStep: selectedElement,
- showSelector: false,
- selectedUuids: shiftKeyPressed ? [...prevState.selectedUuids] : [element.uuid],
- }
- });
- }
-
- unselectElement = (evt: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
- if ((evt.target as any).dataset.click === 'FLOWS') {
- evt.stopPropagation()
- const i = CamelDisplayUtil.setIntegrationVisibility(this.routeDesigner.state.integration, undefined);
- this.routeDesigner.setState(prevState => ({
- integration: i,
- selectedStep: undefined,
- showSelector: false,
- selectedPosition: undefined,
- selectedUuids: [],
- }));
- }
- }
-
- openSelector = (parentId: string | undefined, parentDsl: string | undefined, showSteps: boolean = true, position?: number | undefined, selectorTabIndex?: string | number) => {
- this.routeDesigner.setState({
- showSelector: true,
- parentId: parentId || '',
- parentDsl: parentDsl,
- showSteps: showSteps,
- selectedPosition: position,
- selectorTabIndex: selectorTabIndex
- })
- }
-
- closeDslSelector = () => {
- this.routeDesigner.setState({showSelector: false})
- }
-
- onDslSelect = (dsl: DslMetaModel, parentId: string, position?: number | undefined) => {
- switch (dsl.dsl) {
- case 'FromDefinition' :
- const route = CamelDefinitionApi.createRouteDefinition({from: new FromDefinition({uri: dsl.uri})});
- this.addStep(route, parentId, position)
- break;
- case 'ToDefinition' :
- const to = CamelDefinitionApi.createStep(dsl.dsl, {uri: dsl.uri});
- this.addStep(to, parentId, position)
- break;
- case 'ToDynamicDefinition' :
- const toD = CamelDefinitionApi.createStep(dsl.dsl, {uri: dsl.uri});
- this.addStep(toD, parentId, position)
- break;
- case 'KameletDefinition' :
- const kamelet = CamelDefinitionApi.createStep(dsl.dsl, {name: dsl.name});
- this.addStep(kamelet, parentId, position)
- break;
- default:
- const step = CamelDefinitionApi.createStep(dsl.dsl, undefined);
- const augmentedStep = this.setDslDefaults(step);
- this.addStep(augmentedStep, parentId, position)
- break;
- }
- }
-
- setDslDefaults(step: CamelElement): CamelElement {
- if (step.dslName === 'LogDefinition') {
- // eslint-disable-next-line no-template-curly-in-string
- (step as LogDefinition).message = "${body}";
- }
- if (step.dslName === 'ChoiceDefinition') {
- (step as ChoiceDefinition).when?.push(CamelDefinitionApi.createStep('WhenDefinition', undefined));
- (step as ChoiceDefinition).otherwise = CamelDefinitionApi.createStep('OtherwiseDefinition', undefined);
- }
- return step;
- }
-
- createRouteConfiguration = () => {
- const clone = CamelUtil.cloneIntegration(this.routeDesigner.state.integration);
- const routeConfiguration = new RouteConfigurationDefinition();
- const i = CamelDefinitionApiExt.addRouteConfigurationToIntegration(clone, routeConfiguration);
- this.routeDesigner.setState(prevState => ({
- integration: i,
- propertyOnly: false,
- key: Math.random().toString(),
- selectedStep: routeConfiguration,
- selectedUuids: [routeConfiguration.uuid],
- }));
- }
-
- addStep = (step: CamelElement, parentId: string, position?: number | undefined) => {
- const i = CamelDefinitionApiExt.addStepToIntegration(this.routeDesigner.state.integration, step, parentId, position);
- const clone = CamelUtil.cloneIntegration(i);
- EventBus.sendPosition("clean", step, undefined, new DOMRect(), new DOMRect(), 0);
- const selectedStep = step.dslName === 'RouteDefinition' ? (step as RouteDefinition).from : step;
- this.routeDesigner.setState(prevState => ({
- integration: clone,
- key: Math.random().toString(),
- showSelector: false,
- selectedStep: selectedStep,
- propertyOnly: false,
- selectedUuids: [selectedStep.uuid],
- }));
- }
-
- onIntegrationUpdate = (i: Integration) => {
- this.routeDesigner.setState({integration: i, propertyOnly: false, showSelector: false, key: Math.random().toString()});
- }
-
- moveElement = (source: string, target: string, asChild: boolean) => {
- const i = CamelDefinitionApiExt.moveRouteElement(this.routeDesigner.state.integration, source, target, asChild);
- const clone = CamelUtil.cloneIntegration(i);
- const selectedStep = CamelDefinitionApiExt.findElementInIntegration(clone, source);
- this.routeDesigner.setState(prevState => ({
- integration: clone,
- key: Math.random().toString(),
- showSelector: false,
- selectedStep: selectedStep,
- propertyOnly: false,
- selectedUuids: [source],
- }));
- }
-
- onResizePage(el: HTMLDivElement | null) {
- const rect = el?.getBoundingClientRect();
- if (el && rect && (el.scrollWidth !== this.routeDesigner.state.width || el.scrollHeight !== this.routeDesigner.state.height || rect.top !== this.routeDesigner.state.top || rect.left !== this.routeDesigner.state.left)) {
- this.routeDesigner.setState({width: el.scrollWidth, height: el.scrollHeight, top: rect.top, left: rect.left})
- }
- }
-
- downloadIntegrationImage(dataUrl: string) {
- const a = document.createElement('a');
- a.setAttribute('download', 'karavan-routes.png');
- a.setAttribute('href', dataUrl);
- a.click();
- }
-
- integrationImageDownloadFilter = (node: HTMLElement) => {
- const exclusionClasses = ['add-flow'];
- return !exclusionClasses.some(classname => {
- return node.classList === undefined ? false : node.classList.contains(classname);
- });
- }
-
- integrationImageDownload() {
- if (this.routeDesigner.state.printerRef.current === null) {
- return
- }
- toPng(this.routeDesigner.state.printerRef.current, {
- style: {overflow: 'hidden'}, cacheBust: true, filter: this.integrationImageDownloadFilter,
- height: this.routeDesigner.state.height, width: this.routeDesigner.state.width, backgroundColor: this.routeDesigner.props.dark ? "black" : "white"
- }).then(v => {
- toPng(this.routeDesigner.state.printerRef.current, {
- style: {overflow: 'hidden'}, cacheBust: true, filter: this.integrationImageDownloadFilter,
- height: this.routeDesigner.state.height, width: this.routeDesigner.state.width, backgroundColor: this.routeDesigner.props.dark ? "black" : "white"
- }).then(this.downloadIntegrationImage);
- })
- }
-}
\ No newline at end of file
diff --git a/karavan-designer/src/designer/route/property/ComponentParameterField.tsx b/karavan-designer/src/designer/route/property/ComponentParameterField.tsx
index 69175d98..34c6cadb 100644
--- a/karavan-designer/src/designer/route/property/ComponentParameterField.tsx
+++ b/karavan-designer/src/designer/route/property/ComponentParameterField.tsx
@@ -14,23 +14,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import React from 'react';
+import React, {useRef, useState} from 'react';
import {
- FormGroup,
- TextInput,
- Popover,
- Switch,
- InputGroup,
- TextArea,
- Tooltip,
- Button,
- capitalize, InputGroupItem
+ FormGroup,
+ TextInput,
+ Popover,
+ Switch,
+ InputGroup,
+ TextArea,
+ Tooltip,
+ Button,
+ capitalize, InputGroupItem
} from '@patternfly/react-core';
import {
- Select,
- SelectVariant,
- SelectDirection,
- SelectOption
+ Select,
+ SelectVariant,
+ SelectDirection,
+ SelectOption
} from '@patternfly/react-core/deprecated';
import '../../karavan.css';
import "@patternfly/patternfly/patternfly.css";
@@ -48,70 +48,68 @@ import DockerIcon from "@patternfly/react-icons/dist/js/icons/docker-icon";
import ShowIcon from "@patternfly/react-icons/dist/js/icons/eye-icon";
import HideIcon from "@patternfly/react-icons/dist/js/icons/eye-slash-icon";
import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon";
+import {usePropertiesHook} from "../usePropertiesHook";
+import {useDesignerStore, useIntegrationStore} from "../../KaravanStore";
+import {shallow} from "zustand/shallow";
const prefix = "parameters";
const beanPrefix = "#bean:";
interface Props {
property: ComponentProperty,
- integration: Integration,
element?: CamelElement,
value: any,
onParameterChange?: (parameter: string, value: string | number | boolean | any, pathParameter?: boolean, newRoute?: RouteToCreate) => void
}
-interface State {
- selectStatus: Map<string, boolean>
- showEditor: boolean
- showPassword: boolean
- showInfrastructureSelector: boolean
- infrastructureSelectorProperty?: string
- ref: any
- id: string
-}
+export function ComponentParameterField(props: Props) {
-export class ComponentParameterField extends React.Component<Props, State> {
+ const {onParametersChange, getInternalComponentName} = usePropertiesHook();
- public state: State = {
- selectStatus: new Map<string, boolean>(),
- showEditor: false,
- showPassword: false,
- showInfrastructureSelector: false,
- ref: React.createRef(),
- id: prefix + "-" + this.props.property.name
- }
+ const [integration] = useIntegrationStore((state) => [state.integration], shallow)
+
+ const [selectStatus, setSelectStatus] = useState<Map<string, boolean>>(new Map<string, boolean>());
+ const [showEditor, setShowEditor] = useState<boolean>(false);
+ const [showPassword, setShowPassword] = useState<boolean>(false);
+ const [infrastructureSelector, setInfrastructureSelector] = useState<boolean>(false);
+ const [infrastructureSelectorProperty, setInfrastructureSelectorProperty] = useState<string | undefined>(undefined);
- parametersChanged = (parameter: string, value: string | number | boolean | any, pathParameter?: boolean, newRoute?: RouteToCreate) => {
- this.props.onParameterChange?.call(this, parameter, value, pathParameter, newRoute);
- this.setState({selectStatus: new Map<string, boolean>([[parameter, false]])});
+ const [id, setId] = useState<string>(prefix + "-" + props.property.name);
+ const ref = useRef<any>(null);
+
+
+ function parametersChanged(parameter: string, value: string | number | boolean | any, pathParameter?: boolean, newRoute?: RouteToCreate) {
+ onParametersChange(parameter, value, pathParameter, newRoute);
+ setSelectStatus(new Map<string, boolean>([[parameter, false]]))
}
- openSelect = (propertyName: string, isExpanded: boolean) => {
- this.setState({selectStatus: new Map<string, boolean>([[propertyName, isExpanded]])});
+ function openSelect(propertyName: string, isExpanded: boolean) {
+ setSelectStatus(new Map<string, boolean>([[propertyName, isExpanded]]))
}
- isSelectOpen = (propertyName: string): boolean => {
- return this.state.selectStatus.has(propertyName) && this.state.selectStatus.get(propertyName) === true;
+ function isSelectOpen(propertyName: string): boolean {
+ return selectStatus.has(propertyName) && selectStatus.get(propertyName) === true;
}
- getSelectBean = (property: ComponentProperty, value: any) => {
+ function getSelectBean(property: ComponentProperty, value: any) {
const selectOptions: JSX.Element[] = [];
- const beans = CamelUi.getBeans(this.props.integration);
+ const beans = CamelUi.getBeans(integration);
if (beans) {
selectOptions.push(<SelectOption key={0} value={"Select..."} isPlaceholder/>);
- selectOptions.push(...beans.map((bean) => <SelectOption key={bean.name} value={beanPrefix + bean.name} description={bean.type}/>));
+ selectOptions.push(...beans.map((bean) => <SelectOption key={bean.name} value={beanPrefix + bean.name}
+ description={bean.type}/>));
}
return (
<Select
- id={this.state.id} name={this.state.id}
+ id={id} name={id}
variant={SelectVariant.single}
aria-label={property.name}
onToggle={(_event, isExpanded) => {
- this.openSelect(property.name, isExpanded)
+ openSelect(property.name, isExpanded)
}}
- onSelect={(e, value, isPlaceholder) => this.parametersChanged(property.name, (!isPlaceholder ? value : undefined))}
+ onSelect={(e, value, isPlaceholder) => parametersChanged(property.name, (!isPlaceholder ? value : undefined))}
selections={value}
- isOpen={this.isSelectOpen(property.name)}
+ isOpen={isSelectOpen(property.name)}
aria-labelledby={property.name}
direction={SelectDirection.down}
>
@@ -120,30 +118,19 @@ export class ComponentParameterField extends React.Component<Props, State> {
)
}
- canBeInternalUri = (property: ComponentProperty): boolean => {
- if (this.props.element && this.props.element.dslName === 'ToDefinition' && property.name === 'name') {
- const uri: string = (this.props.element as ToDefinition).uri || '';
+ function canBeInternalUri(property: ComponentProperty): boolean {
+ if (props.element && props.element.dslName === 'ToDefinition' && property.name === 'name') {
+ const uri: string = (props.element as ToDefinition).uri || '';
return uri.startsWith("direct") || uri.startsWith("seda");
} else {
return false;
}
}
- getInternalComponentName = (property: ComponentProperty): string => {
- if (this.props.element && this.props.element.dslName === 'ToDefinition' && property.name === 'name') {
- const uri: string = (this.props.element as ToDefinition).uri || '';
- if (uri.startsWith("direct")) return "direct";
- if (uri.startsWith("seda")) return "seda";
- return '';
- } else {
- return '';
- }
- }
-
- getInternalUriSelect = (property: ComponentProperty, value: any) => {
+ function getInternalUriSelect(property: ComponentProperty, value: any) {
const selectOptions: JSX.Element[] = [];
- const componentName = this.getInternalComponentName(property);
- const internalUris = CamelUi.getInternalRouteUris(this.props.integration, componentName, false);
+ const componentName = getInternalComponentName(property.name, props.element);
+ const internalUris = CamelUi.getInternalRouteUris(integration, componentName, false);
const uris: string [] = [];
uris.push(...internalUris);
if (value && value.length > 0 && !uris.includes(value)) {
@@ -153,111 +140,115 @@ export class ComponentParameterField extends React.Component<Props, State> {
selectOptions.push(...uris.map((value: string) =>
<SelectOption key={value} value={value ? value.trim() : value}/>));
}
- return <InputGroup id={this.state.id} name={this.state.id}>
- <InputGroupItem><Select
- id={this.state.id} name={this.state.id}
- placeholderText="Select or type an URI"
- variant={SelectVariant.typeahead}
- aria-label={property.name}
- onToggle={(_event, isExpanded) => {
- this.openSelect(property.name, isExpanded)
- }}
- onSelect={(e, value, isPlaceholder) => {
- this.parametersChanged(property.name, (!isPlaceholder ? value : undefined), property.kind === 'path', undefined);
- }}
- selections={value}
- isOpen={this.isSelectOpen(property.name)}
- isCreatable={true}
- createText=""
- isInputFilterPersisted={true}
- aria-labelledby={property.name}
- direction={SelectDirection.down}
- >
- {selectOptions}
- </Select></InputGroupItem>
- <InputGroupItem><Tooltip position="bottom-end" content={"Create route"}>
- <Button isDisabled={value === undefined} variant="control" onClick={e => {
- if (value) {
- const newRoute = !internalUris.includes(value.toString()) ? new RouteToCreate(componentName, value.toString()) : undefined;
- this.parametersChanged(property.name, value, property.kind === 'path', newRoute);
- }
- }}>
- {<PlusIcon/>}
- </Button>
- </Tooltip></InputGroupItem>
+ return <InputGroup id={id} name={id}>
+ <InputGroupItem isFill>
+ <Select
+ id={id} name={id}
+ placeholderText="Select or type an URI"
+ variant={SelectVariant.typeahead}
+ aria-label={property.name}
+ onToggle={(_event, isExpanded) => {
+ openSelect(property.name, isExpanded)
+ }}
+ onSelect={(e, value, isPlaceholder) => {
+ parametersChanged(property.name, (!isPlaceholder ? value : undefined), property.kind === 'path', undefined);
+ }}
+ selections={value}
+ isOpen={isSelectOpen(property.name)}
+ isCreatable={true}
+ createText=""
+ isInputFilterPersisted={true}
+ aria-labelledby={property.name}
+ direction={SelectDirection.down}>
+ {selectOptions}
+ </Select>
+ </InputGroupItem>
+ <InputGroupItem>
+ <Tooltip position="bottom-end" content={"Create route"}>
+ <Button isDisabled={value === undefined} variant="control" onClick={e => {
+ if (value) {
+ const newRoute = !internalUris.includes(value.toString())
+ ? CamelUi.createNewInternalRoute(componentName.concat(...':',value.toString()))
+ : undefined;
+ parametersChanged(property.name, value, property.kind === 'path', newRoute);
+ }
+ }}>
+ {<PlusIcon/>}
+ </Button>
+ </Tooltip>
+ </InputGroupItem>
</InputGroup>
}
- selectInfrastructure = (value: string) => {
+ function selectInfrastructure(value: string) {
// check if there is a selection
- const textVal = this.state.ref.current;
+ const textVal = ref.current;
const cursorStart = textVal.selectionStart;
const cursorEnd = textVal.selectionEnd;
- if (cursorStart !== cursorEnd){
- const prevValue = this.props.value;
+ if (cursorStart !== cursorEnd) {
+ const prevValue = props.value;
const selectedText = prevValue.substring(cursorStart, cursorEnd)
value = prevValue.replace(selectedText, value);
}
- const propertyName = this.state.infrastructureSelectorProperty;
+ const propertyName = infrastructureSelectorProperty;
if (propertyName) {
if (value.startsWith("config") || value.startsWith("secret")) value = "{{" + value + "}}";
- this.parametersChanged(propertyName, value);
- this.setState({showInfrastructureSelector: false, infrastructureSelectorProperty: undefined})
+ parametersChanged(propertyName, value);
+ setInfrastructureSelector(false);
+ setInfrastructureSelectorProperty(undefined);
}
}
- openInfrastructureSelector = (propertyName: string) => {
- this.setState({infrastructureSelectorProperty: propertyName, showInfrastructureSelector: true});
+ function openInfrastructureSelector(propertyName: string) {
+ setInfrastructureSelector(true);
+ setInfrastructureSelectorProperty(propertyName);
}
- closeInfrastructureSelector = () => {
- this.setState({showInfrastructureSelector: false})
- }
- getInfrastructureSelectorModal() {
+ function getInfrastructureSelectorModal() {
return (
<InfrastructureSelector
dark={false}
- isOpen={this.state.showInfrastructureSelector}
- onClose={() => this.closeInfrastructureSelector()}
- onSelect={this.selectInfrastructure}/>)
+ isOpen={infrastructureSelector}
+ onClose={() => setInfrastructureSelector(false)}
+ onSelect={selectInfrastructure}/>)
}
- getStringInput(property: ComponentProperty, value: any) {
- const {showEditor, showPassword} = this.state;
+ function getStringInput(property: ComponentProperty, value: any) {
const inInfrastructure = InfrastructureAPI.infrastructure !== 'local';
const noInfraSelectorButton = ["uri", "id", "description", "group"].includes(property.name);
const icon = InfrastructureAPI.infrastructure === 'kubernetes' ? <KubernetesIcon/> : <DockerIcon/>
return <InputGroup>
{inInfrastructure && !showEditor && !noInfraSelectorButton &&
- <Tooltip position="bottom-end" content={"Select from " + capitalize((InfrastructureAPI.infrastructure))}>
- <Button variant="control" onClick={e => this.openInfrastructureSelector(property.name)}>
+ <Tooltip position="bottom-end"
+ content={"Select from " + capitalize((InfrastructureAPI.infrastructure))}>
+ <Button variant="control" onClick={e => openInfrastructureSelector(property.name)}>
{icon}
</Button>
</Tooltip>}
{(!showEditor || property.secret) &&
- <TextInput className="text-field" isRequired ref={this.state.ref}
+ <TextInput className="text-field" isRequired ref={ref}
type={property.secret && !showPassword ? "password" : "text"}
- id={this.state.id} name={this.state.id}
+ id={id} name={id}
value={value !== undefined ? value : property.defaultValue}
- onChange={(e, value) => this.parametersChanged(property.name, value, property.kind === 'path')}/>}
+ onChange={(e, value) => parametersChanged(property.name, value, property.kind === 'path')}/>}
{showEditor && !property.secret &&
- <TextArea autoResize={true} ref={this.state.ref}
+ <TextArea autoResize={true} ref={ref}
className="text-field" isRequired
type="text"
- id={this.state.id} name={this.state.id}
+ id={id} name={id}
value={value !== undefined ? value : property.defaultValue}
- onChange={(e, value) => this.parametersChanged(property.name, value, property.kind === 'path')}/>}
+ onChange={(e, value) => parametersChanged(property.name, value, property.kind === 'path')}/>}
{!property.secret &&
<Tooltip position="bottom-end" content={showEditor ? "Change to TextField" : "Change to Text Area"}>
- <Button variant="control" onClick={e => this.setState({showEditor: !showEditor})}>
+ <Button variant="control" onClick={e => setShowEditor(!showEditor)}>
{showEditor ? <CompressIcon/> : <ExpandIcon/>}
</Button>
</Tooltip>
}
{property.secret &&
<Tooltip position="bottom-end" content={showPassword ? "Hide" : "Show"}>
- <Button variant="control" onClick={e => this.setState({showPassword: !showPassword})}>
+ <Button variant="control" onClick={e => setShowPassword(!showPassword)}>
{showPassword ? <ShowIcon/> : <HideIcon/>}
</Button>
</Tooltip>
@@ -265,20 +256,20 @@ export class ComponentParameterField extends React.Component<Props, State> {
</InputGroup>
}
- getTextInput = (property: ComponentProperty, value: any) => {
+ function getTextInput(property: ComponentProperty, value: any) {
return (
<TextInput
className="text-field" isRequired
type={['integer', 'int', 'number'].includes(property.type) ? 'number' : (property.secret ? "password" : "text")}
- id={this.state.id} name={this.state.id}
+ id={id} name={id}
value={value !== undefined ? value : property.defaultValue}
- onChange={(e, value) => {
- this.parametersChanged(property.name, ['integer', 'int', 'number'].includes(property.type) ? Number(value) : value, property.kind === 'path')
+ onChange={(_, value) => {
+ parametersChanged(property.name, ['integer', 'int', 'number'].includes(property.type) ? Number(value) : value, property.kind === 'path')
}}/>
)
}
- getSelect = (property: ComponentProperty, value: any) => {
+ function getSelect(property: ComponentProperty, value: any) {
const selectOptions: JSX.Element[] = []
if (property.enum && property.enum.length > 0) {
selectOptions.push(<SelectOption key={0} value={"Select ..."} isPlaceholder/>);
@@ -286,15 +277,15 @@ export class ComponentParameterField extends React.Component<Props, State> {
}
return (
<Select
- id={this.state.id} name={this.state.id}
+ id={id} name={id}
variant={SelectVariant.single}
aria-label={property.name}
onToggle={(_event, isExpanded) => {
- this.openSelect(property.name, isExpanded)
+ openSelect(property.name, isExpanded)
}}
- onSelect={(e, value, isPlaceholder) => this.parametersChanged(property.name, (!isPlaceholder ? value : undefined), property.kind === 'path')}
+ onSelect={(e, value, isPlaceholder) => parametersChanged(property.name, (!isPlaceholder ? value : undefined), property.kind === 'path')}
selections={value !== undefined ? value.toString() : property.defaultValue}
- isOpen={this.isSelectOpen(property.name)}
+ isOpen={isSelectOpen(property.name)}
aria-labelledby={property.name}
direction={SelectDirection.down}
>
@@ -303,56 +294,53 @@ export class ComponentParameterField extends React.Component<Props, State> {
)
}
- getSwitch = (property: ComponentProperty, value: any) => {
+ function getSwitch(property: ComponentProperty, value: any) {
return (
<Switch
- id={this.state.id} name={this.state.id}
+ id={id} name={id}
value={value?.toString()}
- aria-label={this.state.id}
+ aria-label={id}
isChecked={value !== undefined ? Boolean(value) : property.defaultValue !== undefined && property.defaultValue === 'true'}
- onChange={e => this.parametersChanged(property.name, !Boolean(value))}/>
+ onChange={e => parametersChanged(property.name, !Boolean(value))}/>
)
}
- render() {
- const property: ComponentProperty = this.props.property;
- const value = this.props.value;
- const id = this.state.id;
- return (
- <FormGroup
- key={id}
- label={property.displayName}
- isRequired={property.required}
- labelIcon={
- <Popover
- position={"left"}
- headerContent={property.displayName}
- bodyContent={property.description}
- footerContent={
- <div>
- {property.defaultValue !== undefined && <div>{"Default: " + property.defaultValue}</div>}
- {property.required && <div>{property.displayName + " is required"}</div>}
- </div>
- }>
- <button type="button" aria-label="More info" onClick={e => e.preventDefault()}
- className="pf-v5-c-form__group-label-help">
- <HelpIcon />
- </button>
- </Popover>
- }>
- {this.canBeInternalUri(property) && this.getInternalUriSelect(property, value)}
- {property.type === 'string' && property.enum === undefined && !this.canBeInternalUri(property)
- && this.getStringInput(property, value)}
- {['duration', 'integer', 'int', 'number'].includes(property.type) && property.enum === undefined && !this.canBeInternalUri(property)
- && this.getTextInput(property, value)}
- {['object'].includes(property.type) && !property.enum
- && this.getSelectBean(property, value)}
- {['string', 'object'].includes(property.type) && property.enum
- && this.getSelect(property, value)}
- {property.type === 'boolean'
- && this.getSwitch(property, value)}
- {this.getInfrastructureSelectorModal()}
- </FormGroup>
- )
- }
+ const property: ComponentProperty = props.property;
+ const value = props.value;
+ return (
+ <FormGroup
+ key={id}
+ label={property.displayName}
+ isRequired={property.required}
+ labelIcon={
+ <Popover
+ position={"left"}
+ headerContent={property.displayName}
+ bodyContent={property.description}
+ footerContent={
+ <div>
+ {property.defaultValue !== undefined && <div>{"Default: " + property.defaultValue}</div>}
+ {property.required && <div>{property.displayName + " is required"}</div>}
+ </div>
+ }>
+ <button type="button" aria-label="More info" onClick={e => e.preventDefault()}
+ className="pf-v5-c-form__group-label-help">
+ <HelpIcon/>
+ </button>
+ </Popover>
+ }>
+ {canBeInternalUri(property) && getInternalUriSelect(property, value)}
+ {property.type === 'string' && property.enum === undefined && !canBeInternalUri(property)
+ && getStringInput(property, value)}
+ {['duration', 'integer', 'int', 'number'].includes(property.type) && property.enum === undefined && !canBeInternalUri(property)
+ && getTextInput(property, value)}
+ {['object'].includes(property.type) && !property.enum
+ && getSelectBean(property, value)}
+ {['string', 'object'].includes(property.type) && property.enum
+ && getSelect(property, value)}
+ {property.type === 'boolean'
+ && getSwitch(property, value)}
+ {getInfrastructureSelectorModal()}
+ </FormGroup>
+ )
}
diff --git a/karavan-designer/src/designer/route/property/DataFormatField.tsx b/karavan-designer/src/designer/route/property/DataFormatField.tsx
index ef447b6f..71fd3402 100644
--- a/karavan-designer/src/designer/route/property/DataFormatField.tsx
+++ b/karavan-designer/src/designer/route/property/DataFormatField.tsx
@@ -14,15 +14,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import React from 'react';
+import React, {useState} from 'react';
import {
- ExpandableSection
+ ExpandableSection
} from '@patternfly/react-core';
import {
- Select,
- SelectVariant,
- SelectDirection,
- SelectOption
+ Select,
+ SelectVariant,
+ SelectDirection,
+ SelectOption
} from '@patternfly/react-core/deprecated';
import '../../karavan.css';
import "@patternfly/patternfly/patternfly.css";
@@ -42,135 +42,107 @@ interface Props {
dark: boolean,
}
-interface State {
- selectIsOpen: boolean
- dataFormat: string
- isShowAdvanced: boolean
-}
-
-export class DataFormatField extends React.Component<Props, State> {
+export function DataFormatField(props: Props) {
- public state: State = {
- selectIsOpen: false,
- dataFormat: CamelDefinitionApiExt.getDataFormat(this.props.value)?.name || "json",
- isShowAdvanced: false
- }
+ const [selectIsOpen, setSelectIsOpen] = useState<boolean>(false);
+ const [showAdvanced, setShowAdvanced] = useState<boolean>(false);
- componentDidMount() {
- if (CamelDefinitionApiExt.getDataFormat(this.props.value)?.name === undefined) {
- this.dataFormatChanged("json", CamelDefinitionApi.createDataFormat('JsonDataFormat', {}));
- }
+ function getDataFormatString() {
+ return CamelDefinitionApiExt.getDataFormat(props.value)?.name || 'json';
}
- componentDidUpdate = (prevProps: Readonly<Props>, prevState: Readonly<State>, snapshot?: any) => {
- const newDataFormat = CamelDefinitionApiExt.getDataFormat(this.props.value)?.name || "json"
- if (prevProps.value
- && (prevProps.value.uuid !== this.props.value.uuid
- || prevState.dataFormat !== newDataFormat)
- ) {
- this.setState({
- dataFormat: newDataFormat
- });
- }
+ function openSelect() {
+ setSelectIsOpen(true)
}
- openSelect = () => {
- this.setState({selectIsOpen: true});
- }
-
- dataFormatChanged = (dataFormat: string, value?: CamelElement) => {
+ function dataFormatChanged(dataFormat: string, value?: CamelElement) {
if (dataFormat !== (value as any).dataFormatName) {
const className = CamelMetadataApi.getCamelDataFormatMetadataByName(dataFormat)?.className;
value = CamelDefinitionApi.createDataFormat(className || '', {}); // perhaps copy other similar fields later
}
- const df = CamelDefinitionApi.createStep(this.props.dslName, {});
+ const df = CamelDefinitionApi.createStep(props.dslName, {});
(df as any)[dataFormat] = value;
- this.props.onDataFormatChange?.call(this, df);
- this.setState({selectIsOpen: false});
+ (df as any)['uuid'] = props.value.uuid;
+ (df as any)['id'] = (props.value as any)['id'];
+
+ props.onDataFormatChange?.(df);
+ setSelectIsOpen(false);
}
- propertyChanged = (fieldId: string, value: string | number | boolean | any) => {
- const df = this.getDataFormatValue();
+ function propertyChanged(fieldId: string, value: string | number | boolean | any) {
+ const df = getDataFormatValue();
if (df) {
(df as any)[fieldId] = value;
- this.dataFormatChanged(this.state.dataFormat, df);
+ dataFormatChanged(getDataFormatString(), df);
}
}
- getDataFormatValue = (): CamelElement => {
- return (this.props.value as any)[this.state.dataFormat]
- ? (this.props.value as any)[this.state.dataFormat]
- : CamelDefinitionApi.createDataFormat(this.state.dataFormat, (this.props.value as any)[this.state.dataFormat]);
+ function getDataFormatValue(): CamelElement {
+ const dataFormatString = getDataFormatString();
+ return (props.value as any)[dataFormatString]
+ ? (props.value as any)[dataFormatString]
+ : CamelDefinitionApi.createDataFormat(dataFormatString, (props.value as any)[dataFormatString]);
}
- getPropertyFields = (value: any, properties: PropertyMeta[]) => {
+ function getPropertyFields(value: any, properties: PropertyMeta[]) {
return (<>
{value && properties?.map((property: PropertyMeta) =>
- <DslPropertyField key={property.name} property={property}
- integration={this.props.integration}
- element={value}
- value={value ? (value as any)[property.name] : undefined}
- onExpressionChange={exp => {
- }}
- onParameterChange={parameter => {
- console.log(parameter)
- }}
- onDataFormatChange={dataFormat => {
- console.log(dataFormat)
- }}
- dark={this.props.dark}
- onChange={this.propertyChanged}/>
+ <DslPropertyField
+ key={property.name}
+ property={property}
+ value={value ? (value as any)[property.name] : undefined}
+ onPropertyChange={propertyChanged}
+ />
)}
</>)
}
- render() {
- const value = this.getDataFormatValue();
- const dataFormat = DataFormats.find((l: [string, string, string]) => l[0] === this.state.dataFormat);
- const properties = CamelDefinitionApiExt.getElementPropertiesByName(this.state.dataFormat).sort((a, b) => a.name === 'library' ? -1 : 1);
- const propertiesMain = properties.filter(p => !p.label.includes("advanced"));
- const propertiesAdvanced = properties.filter(p => p.label.includes("advanced"));
- const selectOptions: JSX.Element[] = []
- DataFormats.forEach((lang: [string, string, string]) => {
- const s = <SelectOption key={lang[0]} value={lang[0]} description={lang[2]}/>;
- selectOptions.push(s);
- })
- return (
+ const value = getDataFormatValue();
+ const dataFormatString = getDataFormatString();
+ const dataFormat = DataFormats.find((l: [string, string, string]) => l[0] === dataFormatString);
+ const properties = CamelDefinitionApiExt.getElementPropertiesByName(dataFormatString).sort((a, b) => a.name === 'library' ? -1 : 1);
+ const propertiesMain = properties.filter(p => !p.label.includes("advanced"));
+ const propertiesAdvanced = properties.filter(p => p.label.includes("advanced"));
+ const selectOptions: JSX.Element[] = []
+ DataFormats.forEach((lang: [string, string, string]) => {
+ const s = <SelectOption key={lang[0]} value={lang[0]} description={lang[2]}/>;
+ selectOptions.push(s);
+ })
+ return (
+ <div>
<div>
+ <label className="pf-v5-c-form__label" htmlFor="expression">
+ <span className="pf-v5-c-form__label-text">{"Data Format"}</span>
+ <span className="pf-v5-c-form__label-required" aria-hidden="true"> *</span>
+ </label>
+ <Select
+ variant={SelectVariant.typeahead}
+ aria-label={"dataFormat"}
+ onToggle={() => {
+ openSelect()
+ }}
+ onSelect={(_, dataFormat, isPlaceholder) => dataFormatChanged(dataFormat.toString(), value)}
+ selections={dataFormat}
+ isOpen={selectIsOpen}
+ aria-labelledby={"dataFormat"}
+ direction={SelectDirection.down}
+ >
+ {selectOptions}
+ </Select>
+ </div>
+ <div className="object">
<div>
- <label className="pf-v5-c-form__label" htmlFor="expression">
- <span className="pf-v5-c-form__label-text">{"Data Format"}</span>
- <span className="pf-v5-c-form__label-required" aria-hidden="true"> *</span>
- </label>
- <Select
- variant={SelectVariant.typeahead}
- aria-label={"dataFormat"}
- onToggle={() => {
- this.openSelect()
- }}
- onSelect={(e, dataFormat, isPlaceholder) => this.dataFormatChanged(dataFormat.toString(), value)}
- selections={dataFormat}
- isOpen={this.state.selectIsOpen}
- aria-labelledby={"dataFormat"}
- direction={SelectDirection.down}
- >
- {selectOptions}
- </Select>
+ {getPropertyFields(value, propertiesMain)}
+ {propertiesAdvanced.length > 0 &&
+ <ExpandableSection
+ toggleText={'Advanced properties'}
+ onToggle={(_event, isExpanded) => setShowAdvanced(!showAdvanced)}
+ isExpanded={showAdvanced}>
+ {getPropertyFields(value, propertiesAdvanced)}
+ </ExpandableSection>}
</div>
- <div className="object">
- <div>
- {this.getPropertyFields(value, propertiesMain)}
- {propertiesAdvanced.length > 0 &&
- <ExpandableSection
- toggleText={'Advanced properties'}
- onToggle={(_event, isExpanded) => this.setState({isShowAdvanced: !this.state.isShowAdvanced})}
- isExpanded={this.state.isShowAdvanced}>
- {this.getPropertyFields(value, propertiesAdvanced)}
- </ExpandableSection>}
- </div>
- </div>
</div>
- )
- }
+ </div>
+ )
}
\ No newline at end of file
diff --git a/karavan-designer/src/designer/route/property/DslPropertyField.tsx b/karavan-designer/src/designer/route/property/DslPropertyField.tsx
index 3021baf2..aceff154 100644
--- a/karavan-designer/src/designer/route/property/DslPropertyField.tsx
+++ b/karavan-designer/src/designer/route/property/DslPropertyField.tsx
@@ -14,31 +14,31 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import React from 'react';
+import React, {useRef, useState} from 'react';
import {
- FormGroup,
- TextInput,
- Popover,
- Switch,
- ExpandableSection,
- TextArea,
- Chip,
- TextInputGroup,
- TextInputGroupMain,
- TextInputGroupUtilities,
- ChipGroup,
- Button,
- Text,
- Tooltip,
- Card,
- InputGroup,
- capitalize, InputGroupItem
+ FormGroup,
+ TextInput,
+ Popover,
+ Switch,
+ ExpandableSection,
+ TextArea,
+ Chip,
+ TextInputGroup,
+ TextInputGroupMain,
+ TextInputGroupUtilities,
+ ChipGroup,
+ Button,
+ Text,
+ Tooltip,
+ Card,
+ InputGroup,
+ capitalize, InputGroupItem
} from '@patternfly/react-core';
import {
- Select,
- SelectVariant,
- SelectDirection,
- SelectOption
+ Select,
+ SelectVariant,
+ SelectDirection,
+ SelectOption
} from '@patternfly/react-core/deprecated';
import '../../karavan.css';
import "@patternfly/patternfly/patternfly.css";
@@ -50,10 +50,8 @@ import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt"
import {ExpressionField} from "./ExpressionField";
import {CamelUi, RouteToCreate} from "../../utils/CamelUi";
import {ComponentParameterField} from "./ComponentParameterField";
-import {DataFormatDefinition} from "karavan-core/lib/model/CamelDefinition";
-import {Integration, CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
+import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
import {KameletPropertyField} from "./KameletPropertyField";
-import {ExpressionDefinition} from "karavan-core/lib/model/CamelDefinition";
import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon";
import {ObjectField} from "./ObjectField";
import {CamelDefinitionApi} from "karavan-core/lib/api/CamelDefinitionApi";
@@ -66,104 +64,98 @@ import KubernetesIcon from "@patternfly/react-icons/dist/js/icons/openshift-icon
import {InfrastructureSelector} from "./InfrastructureSelector";
import {InfrastructureAPI} from "../../utils/InfrastructureAPI";
import EditorIcon from "@patternfly/react-icons/dist/js/icons/code-icon";
-import {TemplateApi} from "karavan-core/lib/api/TemplateApi";
import {ModalEditor} from "./ModalEditor";
-import {KaravanInstance} from "../../KaravanDesigner";
import DockerIcon from "@patternfly/react-icons/dist/js/icons/docker-icon";
+import {useDesignerStore, useIntegrationStore} from "../../KaravanStore";
+import {shallow} from "zustand/shallow";
+import {DataFormatDefinition, ExpressionDefinition} from "karavan-core/lib/model/CamelDefinition";
interface Props {
property: PropertyMeta,
+ element?: CamelElement
value: any,
- onChange?: (fieldId: string, value: string | number | boolean | any, newRoute?: RouteToCreate) => void,
+ onPropertyChange?: (fieldId: string, value: string | number | boolean | any, newRoute?: RouteToCreate) => void,
onExpressionChange?: (propertyName: string, exp: ExpressionDefinition) => void,
onDataFormatChange?: (value: DataFormatDefinition) => void,
onParameterChange?: (parameter: string, value: string | number | boolean | any, pathParameter?: boolean, newRoute?: RouteToCreate) => void,
- element?: CamelElement
- integration: Integration,
hideLabel?: boolean,
dslLanguage?: [string, string, string],
- dark: boolean
}
-interface State {
- selectStatus: Map<string, boolean>,
- isShowAdvanced: Map<string, boolean>,
- arrayValues: Map<string, string>,
- showEditor: boolean
- infrastructureSelector: boolean
- infrastructureSelectorProperty?: string
- customCode?: string
- ref: any
-}
+export function DslPropertyField(props: Props) {
-export class DslPropertyField extends React.Component<Props, State> {
+ const [integration] = useIntegrationStore((state) => [state.integration], shallow)
+ const [dark] = useDesignerStore((s) => [s.dark], shallow)
- public state: State = {
- selectStatus: new Map<string, boolean>(),
- arrayValues: new Map<string, string>(),
- isShowAdvanced: new Map<string, boolean>(),
- showEditor: false,
- infrastructureSelector: false,
- ref: React.createRef(),
- };
+ const [isShowAdvanced, setIsShowAdvanced] = useState<Map<string, boolean>>(new Map<string, boolean>());
+ const [arrayValues, setArrayValues] = useState<Map<string, string>>(new Map<string, string>());
+ const [selectStatus, setSelectStatus] = useState<Map<string, boolean>>(new Map<string, boolean>());
+ const [showEditor, setShowEditor] = useState<boolean>(false);
+ const [infrastructureSelector, setInfrastructureSelector] = useState<boolean>(false);
+ const [infrastructureSelectorProperty, setInfrastructureSelectorProperty] = useState<string | undefined>(undefined);
+ const [customCode, setCustomCode] = useState<string | undefined>(undefined);
- openSelect = (propertyName: string, isExpanded: boolean) => {
- this.setState({selectStatus: new Map<string, boolean>([[propertyName, isExpanded]])});
+ const ref = useRef<any>(null);
+
+ function openSelect(propertyName: string, isExpanded: boolean) {
+ setSelectStatus(new Map<string, boolean>([[propertyName, isExpanded]]))
}
- clearSelection = (propertyName: string) => {
- this.setState({selectStatus: new Map<string, boolean>([[propertyName, false]])});
- };
+ function clearSelection(propertyName: string) {
+ setSelectStatus(new Map<string, boolean>([[propertyName, false]]))
+ }
- isSelectOpen = (propertyName: string): boolean => {
- return this.state.selectStatus.has(propertyName) && this.state.selectStatus.get(propertyName) === true;
+ function isSelectOpen(propertyName: string): boolean {
+ return selectStatus.get(propertyName) === true;
}
- propertyChanged = (fieldId: string, value: string | number | boolean | any, newRoute?: RouteToCreate) => {
- if (fieldId === 'id' && CamelDefinitionApiExt.hasElementWithId(this.props.integration, value)) {
- value = this.props.value;
+ function propertyChanged(fieldId: string, value: string | number | boolean | any, newRoute?: RouteToCreate) {
+ if (fieldId === 'id' && CamelDefinitionApiExt.hasElementWithId(integration, value)) {
+ value = props.element;
}
- this.props.onChange?.call(this, fieldId, value, newRoute);
- this.setState({selectStatus: new Map<string, boolean>([[fieldId, false]])});
+ props.onPropertyChange?.(fieldId, value, newRoute);
+ clearSelection(fieldId);
}
- arrayChanged = (fieldId: string, value: string) => {
- const tv = this.state.arrayValues;
- tv.set(fieldId, value);
- this.setState({arrayValues: tv});
+ function arrayChanged(fieldId: string, value: string) {
+ setArrayValues(prevState => {
+ prevState.set(fieldId, value);
+ return prevState;
+ })
}
- arrayDeleteValue = (fieldId: string, element: string) => {
- const property: PropertyMeta = this.props.property;
- let value = this.props.value;
+ function arrayDeleteValue(fieldId: string, element: string) {
+ const property: PropertyMeta = props.property;
+ let value = props.value;
if (property.isArray && property.type === 'string') {
- this.propertyChanged(fieldId, (value as any).filter((x: string) => x !== element))
+ propertyChanged(fieldId, (value as any).filter((x: string) => x !== element))
}
}
- arraySave = (fieldId: string) => {
- const newValue = this.state.arrayValues.get(fieldId);
- const property: PropertyMeta = this.props.property;
- let value = this.props.value;
+ function arraySave(fieldId: string) {
+ const newValue = arrayValues.get(fieldId);
+ const property: PropertyMeta = props.property;
+ let value = props.value;
if (newValue !== undefined && newValue.length > 0 && property.isArray && property.type === 'string') {
if (value) (value as any).push(newValue)
else value = [newValue];
}
- this.propertyChanged(fieldId, value);
- this.arrayChanged(fieldId, "");
+ propertyChanged(fieldId, value);
+ arrayChanged(fieldId, "");
}
- getLabel = (property: PropertyMeta, value: any) => {
- if (!this.isMultiValueField(property) && property.isObject && !property.isArray && !["ExpressionDefinition"].includes(property.type)) {
+ function getLabel(property: PropertyMeta, value: any) {
+ if (!isMultiValueField(property) && property.isObject && !property.isArray && !["ExpressionDefinition"].includes(property.type)) {
const tooltip = value ? "Delete " + property.name : "Add " + property.name;
const className = value ? "change-button delete-button" : "change-button add-button";
const x = value ? undefined : CamelDefinitionApi.createStep(property.type, {});
- const icon = value ? (<DeleteIcon />) : (<AddIcon />);
+ const icon = value ? (<DeleteIcon/>) : (<AddIcon/>);
return (
<div style={{display: "flex"}}>
<Text>{property.displayName} </Text>
<Tooltip position={"top"} content={<div>{tooltip}</div>}>
- <button className={className} onClick={e => this.props.onChange?.call(this, property.name, x)} aria-label="Add element">
+ <button className={className} onClick={e => props.onPropertyChange?.(property.name, x)}
+ aria-label="Add element">
{icon}
</button>
</Tooltip>
@@ -174,141 +166,162 @@ export class DslPropertyField extends React.Component<Props, State> {
}
}
- isUriReadOnly = (property: PropertyMeta): boolean => {
- const dslName: string = this.props.element?.dslName || '';
+ function isUriReadOnly(property: PropertyMeta): boolean {
+ const dslName: string = props.element?.dslName || '';
return property.name === 'uri' && !['ToDynamicDefinition', 'WireTapDefinition'].includes(dslName)
}
- selectInfrastructure = (value: string) => {
+ function selectInfrastructure(value: string) {
// check if there is a selection
- const textVal = this.state.ref.current;
- const cursorStart = textVal.selectionStart;
- const cursorEnd = textVal.selectionEnd;
- if (cursorStart !== cursorEnd) {
- const prevValue = this.props.value;
- const selectedText = prevValue.substring(cursorStart, cursorEnd)
- value = prevValue.replace(selectedText, value);
- }
- const propertyName = this.state.infrastructureSelectorProperty;
- if (propertyName) {
- if (value.startsWith("config") || value.startsWith("secret")) value = "{{" + value + "}}";
- this.propertyChanged(propertyName, value);
- this.setState({infrastructureSelector: false, infrastructureSelectorProperty: undefined})
+ const textVal = ref.current;
+ if (textVal != null) {
+ const cursorStart = textVal.selectionStart;
+ const cursorEnd = textVal.selectionEnd;
+ if (cursorStart !== cursorEnd) {
+ const prevValue = props.value;
+ const selectedText = prevValue.substring(cursorStart, cursorEnd)
+ value = prevValue.replace(selectedText, value);
+ }
+ const propertyName = infrastructureSelectorProperty;
+ if (propertyName) {
+ if (value.startsWith("config") || value.startsWith("secret")) value = "{{" + value + "}}";
+ propertyChanged(propertyName, value);
+ setInfrastructureSelector(false);
+ setInfrastructureSelectorProperty(undefined);
+ }
}
}
- openInfrastructureSelector = (propertyName: string) => {
- this.setState({infrastructureSelectorProperty: propertyName, infrastructureSelector: true});
+ function openInfrastructureSelector(propertyName: string) {
+ setInfrastructureSelector(true);
+ setInfrastructureSelectorProperty(propertyName);
}
- closeInfrastructureSelector = () => {
- this.setState({infrastructureSelector: false})
+ function closeInfrastructureSelector() {
+ setInfrastructureSelector(false);
}
- getInfrastructureSelectorModal() {
+ function getInfrastructureSelectorModal() {
return (
<InfrastructureSelector
dark={false}
- isOpen={this.state.infrastructureSelector}
- onClose={() => this.closeInfrastructureSelector()}
- onSelect={this.selectInfrastructure}/>)
+ isOpen={infrastructureSelector}
+ onClose={() => closeInfrastructureSelector()}
+ onSelect={selectInfrastructure}/>)
}
- getStringInput = (property: PropertyMeta, value: any) => {
- const showEditor = this.state.showEditor;
+ function getStringInput(property: PropertyMeta, value: any) {
const inInfrastructure = InfrastructureAPI.infrastructure !== 'local';
const noInfraSelectorButton = ["uri", "id", "description", "group"].includes(property.name);
const icon = InfrastructureAPI.infrastructure === 'kubernetes' ? <KubernetesIcon/> : <DockerIcon/>
return (<InputGroup>
{inInfrastructure && !showEditor && !noInfraSelectorButton &&
- <Tooltip position="bottom-end" content={"Select from " + capitalize(InfrastructureAPI.infrastructure)}>
- <Button variant="control" onClick={e => this.openInfrastructureSelector(property.name)}>
- {icon}
- </Button>
- </Tooltip>}
- {(!showEditor || property.secret) && <TextInput
- ref={this.state.ref}
- className="text-field" isRequired
- type={['integer', 'number'].includes(property.type) ? 'number' : (property.secret ? "password" : "text")}
- id={property.name} name={property.name}
- value={value?.toString()}
- onChange={(_, v) => this.propertyChanged(property.name, ['integer', 'number'].includes(property.type) ? Number(v) : v)}
- readOnlyVariant={this.isUriReadOnly(property) ? "default" : undefined}/>
+ <InputGroupItem>
+ <Tooltip position="bottom-end"
+ content={"Select from " + capitalize(InfrastructureAPI.infrastructure)}>
+ <Button variant="control" onClick={e => openInfrastructureSelector(property.name)}>
+ {icon}
+ </Button>
+ </Tooltip>
+ </InputGroupItem>
}
- {showEditor && !property.secret && <TextArea
- ref={this.state.ref}
- autoResize={true}
- className="text-field" isRequired
- type="text"
- id={property.name} name={property.name}
- value={value?.toString()}
- onChange={(_, v) => this.propertyChanged(property.name, ['integer', 'number'].includes(property.type) ? Number(v) : v)}
- readOnlyVariant={this.isUriReadOnly(property) ? "default" : undefined}/>
+ {(!showEditor || property.secret) &&
+ <InputGroupItem isFill>
+ <TextInput ref={ref}
+ className="text-field" isRequired
+ type={['integer', 'number'].includes(property.type) ? 'number' : (property.secret ? "password" : "text")}
+ id={property.name} name={property.name}
+ value={value?.toString()}
+ onChange={(_, v) => propertyChanged(property.name, ['integer', 'number'].includes(property.type) ? Number(v) : v)}
+ readOnlyVariant={isUriReadOnly(property) ? "default" : undefined}/>
+ </InputGroupItem>
+ }
+ {showEditor && !property.secret &&
+ <InputGroupItem isFill>
+ <TextArea ref={ref}
+ autoResize={true}
+ className="text-field" isRequired
+ type="text"
+ id={property.name} name={property.name}
+ value={value?.toString()}
+ onChange={(_, v) => propertyChanged(property.name, ['integer', 'number'].includes(property.type) ? Number(v) : v)}
+ readOnlyVariant={isUriReadOnly(property) ? "default" : undefined}/>
+ </InputGroupItem>
}
{!property.secret &&
- <Tooltip position="bottom-end" content={showEditor ? "Change to TextField" : "Change to Text Area"}>
- <Button variant="control" onClick={e => this.setState({showEditor: !showEditor})}>
- {showEditor ? <CompressIcon/> : <ExpandIcon/>}
- </Button>
- </Tooltip>
+ <InputGroupItem>
+ <Tooltip position="bottom-end" content={showEditor ? "Change to TextField" : "Change to Text Area"}>
+ <Button variant="control" onClick={e => setShowEditor(!showEditor)}>
+ {showEditor ? <CompressIcon/> : <ExpandIcon/>}
+ </Button>
+ </Tooltip>
+ </InputGroupItem>
}
</InputGroup>)
}
- showCode = (name: string, javaType: string) => {
- const {property} = this.props;
- KaravanInstance.getProps().onGetCustomCode.call(this, name, property.javaType).then(value => {
- if (value === undefined) {
- const code = TemplateApi.generateCode(property.javaType, name);
- this.setState({customCode: code, showEditor: true})
- } else {
- this.setState({customCode: value, showEditor: true})
- }
- }).catch(reason => console.log(reason))
+ function showCode(name: string, javaType: string) {
+ const {property} = props;
+ // TODO:
+ // InfrastructureAPI.onGetCustomCode?.(name, property.javaType).then(value => {
+ // if (value === undefined) {
+ // const code = TemplateApi.generateCode(property.javaType, name);
+ // setShowEditor(true);
+ // setCustomCode(code);
+ // } else {
+ // setShowEditor(true);
+ // setCustomCode(value);
+ // }
+ // }).catch((reason: any) => console.log(reason))
}
- getJavaTypeGeneratedInput = (property: PropertyMeta, value: any) => {
- const {dslLanguage, dark} = this.props;
- const {showEditor, customCode} = this.state;
+ function getJavaTypeGeneratedInput(property: PropertyMeta, value: any) {
+ const {dslLanguage} = props;
return (<InputGroup>
- <InputGroupItem isFill >
+ <InputGroupItem isFill>
<TextInput
- ref={this.state.ref}
- className="text-field" isRequired
+ ref={ref}
+ className="text-field" isRequired
type="text"
id={property.name} name={property.name}
value={value?.toString()}
onChange={(_, value) => {
- this.propertyChanged(property.name, CamelUtil.capitalizeName(value?.replace(/\s/g, '')))
+ propertyChanged(property.name, CamelUtil.capitalizeName(value?.replace(/\s/g, '')))
}}
- readOnlyVariant={this.isUriReadOnly(property) ? "default" : undefined}/>
+ readOnlyVariant={isUriReadOnly(property) ? "default" : undefined}/>
+ </InputGroupItem>
+ <InputGroupItem>
+ <Tooltip position="bottom-end" content={"Create Java Class"}>
+ <Button isDisabled={value?.length === 0} variant="control"
+ onClick={e => showCode(value, property.javaType)}>
+ <PlusIcon/>
+ </Button>
+ </Tooltip>
+ </InputGroupItem>
+ <InputGroupItem>
+ <ModalEditor property={property}
+ customCode={customCode}
+ showEditor={showEditor}
+ dark={dark}
+ dslLanguage={dslLanguage}
+ title="Java Class"
+ onClose={() => setShowEditor(false)}
+ onSave={(fieldId, value1) => {
+ propertyChanged(fieldId, value);
+ // TODO:
+ // KaravanInstance.getProps().onSaveCustomCode?.call(this, value, value1);
+ setShowEditor(false)
+ }}/>
</InputGroupItem>
- <InputGroupItem><Tooltip position="bottom-end" content={"Create Java Class"}>
- <Button isDisabled={value?.length === 0} variant="control" onClick={e => this.showCode(value, property.javaType)}>
- <PlusIcon/>
- </Button>
- </Tooltip></InputGroupItem>
- <InputGroupItem><ModalEditor property={property}
- customCode={customCode}
- showEditor={showEditor}
- dark={dark}
- dslLanguage={dslLanguage}
- title="Java Class"
- onClose={() => this.setState({showEditor: false})}
- onSave={(fieldId, value1) => {
- this.propertyChanged(fieldId, value);
- KaravanInstance.getProps().onSaveCustomCode?.call(this, value, value1);
- this.setState({showEditor: false});
- }}/></InputGroupItem>
</InputGroup>)
}
- getTextArea = (property: PropertyMeta, value: any) => {
- const {dslLanguage, dark} = this.props;
- const {showEditor} = this.state;
+ function getTextArea(property: PropertyMeta, value: any) {
+ const {dslLanguage} = props;
return (
<InputGroup>
- <InputGroupItem isFill ><TextArea
+ <InputGroupItem isFill>
+ <TextArea
autoResize
className="text-field" isRequired
type={"text"}
@@ -316,52 +329,54 @@ export class DslPropertyField extends React.Component<Props, State> {
name={property.name}
height={"100px"}
value={value?.toString()}
- onChange={(_, v) => this.propertyChanged(property.name, v)}/></InputGroupItem>
- <InputGroupItem><Tooltip position="bottom-end" content={"Show Editor"}>
- <Button variant="control" onClick={e => this.setState({showEditor: !showEditor})}>
+ onChange={(_, v) => propertyChanged(property.name, v)}/>
+ </InputGroupItem>
+ <InputGroupItem>
+ <Tooltip position="bottom-end" content={"Show Editor"}>
+ <Button variant="control" onClick={e => setShowEditor(!showEditor)}>
<EditorIcon/>
</Button>
- </Tooltip></InputGroupItem>
- <InputGroupItem><ModalEditor property={property}
- customCode={value}
- showEditor={showEditor}
- dark={dark}
- dslLanguage={dslLanguage}
- title={`Expression (${dslLanguage?.[0]})`}
- onClose={() => this.setState({showEditor: false})}
- onSave={(fieldId, value1) => {
- this.propertyChanged(fieldId, value1);
- this.setState({showEditor: false});
- }}/></InputGroupItem>
+ </Tooltip>
+ </InputGroupItem>
+ {showEditor && <InputGroupItem>
+ <ModalEditor property={property}
+ customCode={value}
+ showEditor={showEditor}
+ dark={dark}
+ dslLanguage={dslLanguage}
+ title={`Expression (${dslLanguage?.[0]})`}
+ onClose={() => setShowEditor(false)}
+ onSave={(fieldId, value1) => {
+ propertyChanged(fieldId, value1);
+ setShowEditor(false);
+ }}/>
+ </InputGroupItem>}
</InputGroup>
)
}
- getExpressionField = (property: PropertyMeta, value: any) => {
+ function getExpressionField(property: PropertyMeta, value: any) {
return (
<div className="expression">
- <ExpressionField property={property}
- value={value}
- onExpressionChange={this.props.onExpressionChange}
- integration={this.props.integration}
- dark={this.props.dark}/>
+ <ExpressionField
+ property={property}
+ value={value}
+ onExpressionChange={props.onExpressionChange}/>
</div>
)
}
- getObjectField = (property: PropertyMeta, value: any) => {
+ function getObjectField(property: PropertyMeta, value: any) {
return (
<div className="object">
{value && <ObjectField property={property}
value={value}
- onPropertyUpdate={this.props.onChange}
- integration={this.props.integration}
- dark={this.props.dark}/>}
+ onPropertyUpdate={propertyChanged}/>}
</div>
)
}
- getSwitch = (property: PropertyMeta, value: any) => {
+ function getSwitch(property: PropertyMeta, value: any) {
const isChecked = value !== undefined ? Boolean(value) : Boolean(property.defaultValue !== undefined && property.defaultValue === 'true');
return (
<Switch
@@ -369,27 +384,28 @@ export class DslPropertyField extends React.Component<Props, State> {
value={value?.toString()}
aria-label={property.name}
isChecked={isChecked}
- onChange={(_, v) => this.propertyChanged(property.name, v)}/>
+ onChange={(_, v) => propertyChanged(property.name, v)}/>
)
}
- getSelectBean = (property: PropertyMeta, value: any) => {
+ function getSelectBean(property: PropertyMeta, value: any) {
const selectOptions: JSX.Element[] = [];
- const beans = CamelUi.getBeans(this.props.integration);
+ const beans = CamelUi.getBeans(integration);
if (beans) {
selectOptions.push(<SelectOption key={0} value={"Select..."} isPlaceholder/>);
- selectOptions.push(...beans.map((bean) => <SelectOption key={bean.name} value={bean.name} description={bean.type}/>));
+ selectOptions.push(...beans.map((bean) => <SelectOption key={bean.name} value={bean.name}
+ description={bean.type}/>));
}
return (
<Select
variant={SelectVariant.single}
aria-label={property.name}
onToggle={(_event, isExpanded) => {
- this.openSelect(property.name, isExpanded)
+ openSelect(property.name, isExpanded)
}}
- onSelect={(e, value, isPlaceholder) => this.propertyChanged(property.name, (!isPlaceholder ? value : undefined))}
+ onSelect={(e, value, isPlaceholder) => propertyChanged(property.name, (!isPlaceholder ? value : undefined))}
selections={value}
- isOpen={this.isSelectOpen(property.name)}
+ isOpen={isSelectOpen(property.name)}
aria-labelledby={property.name}
direction={SelectDirection.down}
>
@@ -398,7 +414,7 @@ export class DslPropertyField extends React.Component<Props, State> {
)
}
- getSelect = (property: PropertyMeta, value: any) => {
+ function getSelect(property: PropertyMeta, value: any) {
const selectOptions: JSX.Element[] = []
if (property.enumVals && property.enumVals.length > 0) {
selectOptions.push(<SelectOption key={0} value={"Select " + property.name} isPlaceholder/>);
@@ -410,11 +426,11 @@ export class DslPropertyField extends React.Component<Props, State> {
variant={SelectVariant.single}
aria-label={property.name}
onToggle={(_event, isExpanded) => {
- this.openSelect(property.name, isExpanded)
+ openSelect(property.name, isExpanded)
}}
- onSelect={(e, value, isPlaceholder) => this.propertyChanged(property.name, (!isPlaceholder ? value : undefined))}
+ onSelect={(e, value, isPlaceholder) => propertyChanged(property.name, (!isPlaceholder ? value : undefined))}
selections={value}
- isOpen={this.isSelectOpen(property.name)}
+ isOpen={isSelectOpen(property.name)}
id={property.name}
aria-labelledby={property.name}
direction={SelectDirection.down}
@@ -424,11 +440,11 @@ export class DslPropertyField extends React.Component<Props, State> {
)
}
- getMediaTypeSelectOptions(filter?: string): JSX.Element [] {
+ function getMediaTypeSelectOptions(filter?: string): JSX.Element [] {
const options: JSX.Element [] = [
- <SelectOption key={0} value="Select Media Type" isPlaceholder />
+ <SelectOption key={0} value="Select Media Type" isPlaceholder/>
];
- const mediaTypes: JSX.Element[] = filter
+ const mediaTypes: JSX.Element[] = filter
? MediaTypes.filter(mt => mt.includes(filter)).map((value: string) =>
<SelectOption key={value} value={value.trim()}/>)
: MediaTypes.map((value: string) =>
@@ -437,30 +453,30 @@ export class DslPropertyField extends React.Component<Props, State> {
return options;
}
- getMediaTypeSelect = (property: PropertyMeta, value: any) => {
+ function getMediaTypeSelect(property: PropertyMeta, value: any) {
return (
<Select
placeholderText="Select Media Type"
variant={SelectVariant.typeahead}
aria-label={property.name}
onToggle={(_event, isExpanded) => {
- this.openSelect(property.name, isExpanded)
+ openSelect(property.name, isExpanded)
}}
- onSelect={(e, value, isPlaceholder) => this.propertyChanged(property.name, (!isPlaceholder ? value : undefined))}
+ onSelect={(e, value, isPlaceholder) => propertyChanged(property.name, (!isPlaceholder ? value : undefined))}
selections={value}
- isOpen={this.isSelectOpen(property.name)}
+ isOpen={isSelectOpen(property.name)}
isCreatable={false}
isInputFilterPersisted={false}
- onFilter={(e, text) => this.getMediaTypeSelectOptions(text)}
+ onFilter={(e, text) => getMediaTypeSelectOptions(text)}
aria-labelledby={property.name}
direction={SelectDirection.down}
>
- {this.getMediaTypeSelectOptions()}
+ {getMediaTypeSelectOptions()}
</Select>
)
}
- canBeInternalUri = (property: PropertyMeta, element?: CamelElement): boolean => {
+ function canBeInternalUri(property: PropertyMeta, element?: CamelElement): boolean {
if (element?.dslName === 'WireTapDefinition' && property.name === 'uri') {
return true;
} else if (element?.dslName === 'SagaDefinition' && ['compensation', 'completion'].includes(property.name)) {
@@ -472,9 +488,9 @@ export class DslPropertyField extends React.Component<Props, State> {
}
}
- canBeMediaType = (property: PropertyMeta, element?: CamelElement): boolean => {
+ function canBeMediaType(property: PropertyMeta, element?: CamelElement): boolean {
if (element
- && ['RestDefinition', 'GetDefinition', 'PostDefinition', 'PutDefinition', 'PatchDefinition', 'DeleteDefinition', 'HeadDefinition'].includes(element?.dslName)
+ && ['RestDefinition', 'GetDefinition', 'PostDefinition', 'PutDefinition', 'PatchDefinition', 'DeleteDefinition', 'HeadDefinition'].includes(element.dslName)
&& ['consumes', 'produces'].includes(property.name)) {
return true;
} else {
@@ -482,34 +498,35 @@ export class DslPropertyField extends React.Component<Props, State> {
}
}
- javaTypeGenerated = (property: PropertyMeta): boolean => {
+ function javaTypeGenerated(property: PropertyMeta): boolean {
return property.javaType.length !== 0;
}
- getInternalUriSelect = (property: PropertyMeta, value: any) => {
+ function getInternalUriSelect(property: PropertyMeta, value: any) {
+ console.log("getInternalUriSelect", property, value)
const selectOptions: JSX.Element[] = [];
- const urls = CamelUi.getInternalRouteUris(this.props.integration, "direct");
- urls.push(...CamelUi.getInternalRouteUris(this.props.integration, "seda"));
+ const urls = CamelUi.getInternalRouteUris(integration, "direct");
+ urls.push(...CamelUi.getInternalRouteUris(integration, "seda"));
if (urls && urls.length > 0) {
selectOptions.push(...urls.map((value: string) =>
<SelectOption key={value} value={value.trim()}/>));
}
return (
+ <InputGroup id={property.name} name={property.name}>
+ <InputGroupItem isFill>
<Select
placeholderText="Select or type an URI"
variant={SelectVariant.typeahead}
aria-label={property.name}
- onClear={event => this.clearSelection(property.name)}
+ onClear={event => propertyChanged(property.name, undefined, undefined)}
onToggle={(_event, isExpanded) => {
- this.openSelect(property.name, isExpanded)
+ openSelect(property.name, isExpanded)
}}
onSelect={(e, value, isPlaceholder) => {
- const url = value.toString().split(":");
- const newRoute = !urls.includes(value.toString()) && (['direct', 'seda'].includes(url[0])) ? new RouteToCreate(url[0], url[1]) : undefined;
- this.propertyChanged(property.name, (!isPlaceholder ? value : undefined), newRoute)
+ propertyChanged(property.name, (!isPlaceholder ? value : undefined), undefined)
}}
selections={value}
- isOpen={this.isSelectOpen(property.name)}
+ isOpen={isSelectOpen(property.name)}
isCreatable={true}
isInputFilterPersisted={true}
aria-labelledby={property.name}
@@ -517,73 +534,87 @@ export class DslPropertyField extends React.Component<Props, State> {
>
{selectOptions}
</Select>
+ </InputGroupItem>
+ <InputGroupItem>
+ <Tooltip position="bottom-end" content={"Create route"}>
+ <Button isDisabled={value === undefined} variant="control" onClick={e => {
+ if (value) {
+ const newRoute = CamelUi.createNewInternalRoute(value);
+ propertyChanged(property.name, value, newRoute)
+ }
+ }}>
+ {<PlusIcon/>}
+ </Button>
+ </Tooltip>
+ </InputGroupItem>
+ </InputGroup>
)
}
- onMultiValueObjectUpdate = (index: number, fieldId: string, value: CamelElement) => {
- const mValue = [...this.props.value];
+ function onMultiValueObjectUpdate(index: number, fieldId: string, value: CamelElement) {
+ const mValue = [...props.value];
mValue[index] = value;
- this.props.onChange?.call(this, fieldId, mValue);
+ props.onPropertyChange?.(fieldId, mValue);
}
- isKeyValueObject(property: PropertyMeta) {
+ function isKeyValueObject(property: PropertyMeta) {
const props = CamelDefinitionApiExt.getElementProperties(property.type);
return props.length === 2 && props.filter(p => p.name === 'key').length === 1 && props.filter(p => p.name === 'value').length === 1;
}
- getMultiObjectFieldProps(property: PropertyMeta, value: any, v: any, index: number, hideLabel: boolean = false) {
+ function getMultiObjectFieldProps(property: PropertyMeta, value: any, v: any, index: number, hideLabel: boolean = false) {
return (<>
<div className="object">
{value && <ObjectField property={property}
hideLabel={hideLabel}
- value={v}
- onPropertyUpdate={(f, v) => this.onMultiValueObjectUpdate(index, f, v)}
- integration={this.props.integration}
- dark={this.props.dark}/>}
+ onPropertyUpdate={(f, v) => onMultiValueObjectUpdate(index, f, v)}
+ />}
</div>
<Button variant="link" className="delete-button" onClick={e => {
const v = Array.from(value);
v.splice(index, 1);
- this.propertyChanged(property.name, v);
+ propertyChanged(property.name, v);
}}><DeleteIcon/></Button>
</>)
}
- getMultiValueObjectField = (property: PropertyMeta, value: any) => {
- const isKeyValue = this.isKeyValueObject(property);
+ function getMultiValueObjectField(property: PropertyMeta, value: any) {
+ const isKeyValue = isKeyValueObject(property);
return (
<div>
{value && Array.from(value).map((v: any, index: number) => {
if (isKeyValue)
return <div key={property + "-" + index} className="object-key-value">
- {this.getMultiObjectFieldProps(property, value, v, index, index > 0)}
+ {getMultiObjectFieldProps(property, value, v, index, index > 0)}
</div>
else
return <Card key={property + "-" + index} className="object-value">
- {this.getMultiObjectFieldProps(property, value, v, index)}
+ {getMultiObjectFieldProps(property, value, v, index)}
</Card>
})}
<Button variant="link" className="add-button"
- onClick={e => this.propertyChanged(property.name, [...value, CamelDefinitionApi.createStep(property.type, {})])}><AddIcon/>{"Add " + property.displayName}
+ onClick={e => propertyChanged(property.name, [...value, CamelDefinitionApi.createStep(property.type, {})])}><AddIcon/>{"Add " + property.displayName}
</Button>
</div>
)
}
- getMultiValueField = (property: PropertyMeta, value: any) => {
+ function getMultiValueField(property: PropertyMeta, value: any) {
return (
<div>
<TextInputGroup className="input-group">
- <TextInputGroupMain value={this.state.arrayValues.get(property.name)} onChange={(e, v) => this.arrayChanged(property.name, v)} onKeyUp={e => {
- if (e.key === 'Enter') this.arraySave(property.name)
+ <TextInputGroupMain value={arrayValues.get(property.name)}
+ onChange={(e, v) => arrayChanged(property.name, v)} onKeyUp={e => {
+ if (e.key === 'Enter') arraySave(property.name)
}}>
<ChipGroup>
{value && Array.from(value).map((v: any, index: number) => (
- <Chip key={"chip-" + index} className="chip" onClick={() => this.arrayDeleteValue(property.name, v)}>{v.toString()}</Chip>))}
+ <Chip key={"chip-" + index} className="chip"
+ onClick={() => arrayDeleteValue(property.name, v)}>{v.toString()}</Chip>))}
</ChipGroup>
</TextInputGroupMain>
<TextInputGroupUtilities>
- <Button variant="plain" onClick={e => this.arraySave(property.name)} aria-label="Add element">
+ <Button variant="plain" onClick={e => arraySave(property.name)} aria-label="Add element">
<PlusIcon/>
</Button>
</TextInputGroupUtilities>
@@ -592,8 +623,8 @@ export class DslPropertyField extends React.Component<Props, State> {
)
}
- getKameletParameters = () => {
- const element = this.props.element;
+ function getKameletParameters() {
+ const element = props.element;
const requiredParameters = CamelUtil.getKameletRequiredParameters(element);
return (
<div className="parameters">
@@ -603,49 +634,48 @@ export class DslPropertyField extends React.Component<Props, State> {
property={property}
value={CamelDefinitionApiExt.getParametersValue(element, property.id)}
required={requiredParameters?.includes(property.id)}
- onParameterChange={this.props.onParameterChange}
/>)}
</div>
)
}
- getMainComponentParameters = (properties: ComponentProperty[]) => {
+ function getMainComponentParameters(properties: ComponentProperty[]) {
+ const element = props.element;
return (
<div className="parameters">
{properties.map(kp => {
- const value = CamelDefinitionApiExt.getParametersValue(this.props.element, kp.name, kp.kind === 'path');
+ const value = CamelDefinitionApiExt.getParametersValue(element, kp.name, kp.kind === 'path');
return (<ComponentParameterField
key={kp.name}
property={kp}
- element={this.props.element}
- integration={this.props.integration}
value={value}
- onParameterChange={this.props.onParameterChange}
+ element={props.element}
+ onParameterChange={props.onParameterChange}
/>)
})}
</div>
)
}
- getExpandableComponentParameters = (properties: ComponentProperty[], label: string) => {
+ function getExpandableComponentParameters(properties: ComponentProperty[], label: string) {
+ const element = props.element;
return (
<ExpandableSection
toggleText={label}
onToggle={(_event, isExpanded) => {
- this.setState(state => {
- state.isShowAdvanced.set(label, !state.isShowAdvanced.get(label));
- return {isShowAdvanced: state.isShowAdvanced};
+ setIsShowAdvanced(prevState => {
+ prevState.set(label, !prevState.get(label));
+ return prevState;
})
}}
- isExpanded={this.state.isShowAdvanced.has(label) && this.state.isShowAdvanced.get(label)}>
+ isExpanded={isShowAdvanced.has(label) && isShowAdvanced.get(label)}>
<div className="parameters">
{properties.map(kp =>
<ComponentParameterField
key={kp.name}
property={kp}
- integration={this.props.integration}
- value={CamelDefinitionApiExt.getParametersValue(this.props.element, kp.name, kp.kind === 'path')}
- onParameterChange={this.props.onParameterChange}
+ value={CamelDefinitionApiExt.getParametersValue(element, kp.name, kp.kind === 'path')}
+ onParameterChange={props.onParameterChange}
/>
)}
</div>
@@ -653,7 +683,7 @@ export class DslPropertyField extends React.Component<Props, State> {
)
}
- getLabelIcon = (property: PropertyMeta) => {
+ function getLabelIcon(property: PropertyMeta) {
return (
property.description
? <Popover
@@ -662,7 +692,8 @@ export class DslPropertyField extends React.Component<Props, State> {
bodyContent={property.description}
footerContent={
<div>
- {property.defaultValue !== undefined && property.defaultValue.toString().trim().length > 0 && <div>{"Default: " + property.defaultValue}</div>}
+ {property.defaultValue !== undefined && property.defaultValue.toString().trim().length > 0 &&
+ <div>{"Default: " + property.defaultValue}</div>}
{property.required && <b>Required</b>}
</div>
}>
@@ -670,7 +701,7 @@ export class DslPropertyField extends React.Component<Props, State> {
e.preventDefault();
e.stopPropagation();
}} className="pf-v5-c-form__group-label-help">
- <HelpIcon />
+ <HelpIcon/>
</button>
</Popover>
: <div></div>
@@ -678,72 +709,72 @@ export class DslPropertyField extends React.Component<Props, State> {
}
- isMultiValueField = (property: PropertyMeta): boolean => {
+ function isMultiValueField(property: PropertyMeta): boolean {
return ['string'].includes(property.type) && property.name !== 'expression' && property.isArray && !property.enumVals;
}
- getComponentParameters(property: PropertyMeta) {
- const properties = CamelUtil.getComponentProperties(this.props.element);
+ function getComponentParameters(property: PropertyMeta) {
+ const element = props.element;
+ const properties = CamelUtil.getComponentProperties(element);
const propertiesMain = properties.filter(p => !p.label.includes("advanced") && !p.label.includes("security") && !p.label.includes("scheduler"));
const propertiesAdvanced = properties.filter(p => p.label.includes("advanced"));
const propertiesScheduler = properties.filter(p => p.label.includes("scheduler"));
const propertiesSecurity = properties.filter(p => p.label.includes("security"));
return (
<>
- {property.name === 'parameters' && this.getMainComponentParameters(propertiesMain)}
- {property.name === 'parameters' && this.props.element && propertiesScheduler.length > 0
- && this.getExpandableComponentParameters(propertiesScheduler, "Scheduler parameters")}
- {property.name === 'parameters' && this.props.element && propertiesSecurity.length > 0
- && this.getExpandableComponentParameters(propertiesSecurity, "Security parameters")}
- {property.name === 'parameters' && this.props.element && propertiesAdvanced.length > 0
- && this.getExpandableComponentParameters(propertiesAdvanced, "Advanced parameters")}
+ {property.name === 'parameters' && getMainComponentParameters(propertiesMain)}
+ {property.name === 'parameters' && element && propertiesScheduler.length > 0
+ && getExpandableComponentParameters(propertiesScheduler, "Scheduler parameters")}
+ {property.name === 'parameters' && element && propertiesSecurity.length > 0
+ && getExpandableComponentParameters(propertiesSecurity, "Security parameters")}
+ {property.name === 'parameters' && element && propertiesAdvanced.length > 0
+ && getExpandableComponentParameters(propertiesAdvanced, "Advanced parameters")}
</>
)
}
- render() {
- const isKamelet = CamelUtil.isKameletComponent(this.props.element);
- const property: PropertyMeta = this.props.property;
- const value = this.props.value;
- return (
- <div>
- <FormGroup
- label={this.props.hideLabel ? undefined : this.getLabel(property, value)}
- isRequired={property.required}
- labelIcon={this.getLabelIcon(property)}>
- {value && ["ExpressionDefinition", "ExpressionSubElementDefinition"].includes(property.type)
- && this.getExpressionField(property, value)}
- {property.isObject && !property.isArray && !["ExpressionDefinition", "ExpressionSubElementDefinition"].includes(property.type)
- && this.getObjectField(property, value)}
- {property.isObject && property.isArray && !this.isMultiValueField(property)
- && this.getMultiValueObjectField(property, value)}
- {property.name === 'expression' && property.type === "string" && !property.isArray
- && this.getTextArea(property, value)}
- {this.canBeInternalUri(property, this.props.element)
- && this.getInternalUriSelect(property, value)}
- {this.canBeMediaType(property, this.props.element)
- && this.getMediaTypeSelect(property, value)}
- {this.javaTypeGenerated(property)
- && this.getJavaTypeGeneratedInput(property, value)}
- {['string', 'duration', 'integer', 'number'].includes(property.type) && property.name !== 'expression' && !property.name.endsWith("Ref")
- && !property.isArray && !property.enumVals
- && !this.canBeInternalUri(property, this.props.element)
- && !this.canBeMediaType(property, this.props.element)
- && !this.javaTypeGenerated(property)
- && this.getStringInput(property, value)}
- {['string'].includes(property.type) && property.name.endsWith("Ref") && !property.isArray && !property.enumVals
- && this.getSelectBean(property, value)}
- {this.isMultiValueField(property)
- && this.getMultiValueField(property, value)}
- {property.type === 'boolean'
- && this.getSwitch(property, value)}
- {property.enumVals
- && this.getSelect(property, value)}
- {isKamelet && property.name === 'parameters' && this.getKameletParameters()}
- {!isKamelet && property.name === 'parameters' && this.getComponentParameters(property)}
- </FormGroup>
- {this.getInfrastructureSelectorModal()}
- </div>
- )
- }
+ const element = props.element;
+ const isKamelet = CamelUtil.isKameletComponent(element);
+ const property: PropertyMeta = props.property;
+ const value = props.value;
+ return (
+ <div>
+ <FormGroup
+ label={props.hideLabel ? undefined : getLabel(property, value)}
+ isRequired={property.required}
+ labelIcon={getLabelIcon(property)}>
+ {value !== undefined && ["ExpressionDefinition", "ExpressionSubElementDefinition"].includes(property.type)
+ && getExpressionField(property, value)}
+ {property.isObject && !property.isArray && !["ExpressionDefinition", "ExpressionSubElementDefinition"].includes(property.type)
+ && getObjectField(property, value)}
+ {property.isObject && property.isArray && !isMultiValueField(property)
+ && getMultiValueObjectField(property, value)}
+ {property.name === 'expression' && property.type === "string" && !property.isArray
+ && getTextArea(property, value)}
+ {canBeInternalUri(property, element)
+ && getInternalUriSelect(property, value)}
+ {canBeMediaType(property, element)
+ && getMediaTypeSelect(property, value)}
+ {javaTypeGenerated(property)
+ && getJavaTypeGeneratedInput(property, value)}
+ {['string', 'duration', 'integer', 'number'].includes(property.type) && property.name !== 'expression' && !property.name.endsWith("Ref")
+ && !property.isArray && !property.enumVals
+ && !canBeInternalUri(property, element)
+ && !canBeMediaType(property, element)
+ && !javaTypeGenerated(property)
+ && getStringInput(property, value)}
+ {['string'].includes(property.type) && property.name.endsWith("Ref") && !property.isArray && !property.enumVals
+ && getSelectBean(property, value)}
+ {isMultiValueField(property)
+ && getMultiValueField(property, value)}
+ {property.type === 'boolean'
+ && getSwitch(property, value)}
+ {property.enumVals
+ && getSelect(property, value)}
+ {isKamelet && property.name === 'parameters' && getKameletParameters()}
+ {!isKamelet && property.name === 'parameters' && getComponentParameters(property)}
+ </FormGroup>
+ {getInfrastructureSelectorModal()}
+ </div>
+ )
}
diff --git a/karavan-designer/src/designer/route/property/ExpressionField.tsx b/karavan-designer/src/designer/route/property/ExpressionField.tsx
index 135c11fe..0aaf81c1 100644
--- a/karavan-designer/src/designer/route/property/ExpressionField.tsx
+++ b/karavan-designer/src/designer/route/property/ExpressionField.tsx
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import React from 'react';
+import React, {useState} from 'react';
import {
FormGroup,
Popover
@@ -31,7 +31,7 @@ import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon";
import {CamelMetadataApi, Languages, PropertyMeta} from "karavan-core/lib/model/CamelMetadata";
import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
import {ExpressionDefinition} from "karavan-core/lib/model/CamelDefinition";
-import {Integration, CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
+import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
import {CamelDefinitionApi} from "karavan-core/lib/api/CamelDefinitionApi";
import {DslPropertyField} from "./DslPropertyField";
import {CamelUi} from "../../utils/CamelUi";
@@ -39,129 +39,120 @@ import {CamelUi} from "../../utils/CamelUi";
interface Props {
property: PropertyMeta,
value: CamelElement,
- onExpressionChange?: (propertyName: string, exp: ExpressionDefinition) => void
- integration: Integration,
- dark: boolean,
+ onExpressionChange?: (propertyName: string, exp: ExpressionDefinition) => void,
}
-interface State {
- selectIsOpen: boolean;
-}
-
-export class ExpressionField extends React.Component<Props, State> {
+export function ExpressionField(props: Props) {
- public state: State = {
- selectIsOpen: false,
- }
+ const [selectIsOpen, setSelectIsOpen] = useState<boolean>(false);
- openSelect = (isExpanded: boolean) => {
- this.setState({selectIsOpen: isExpanded});
+ function openSelect (isExpanded: boolean) {
+ setSelectIsOpen(isExpanded);
}
- expressionChanged = (language: string, value: CamelElement) => {
+ function expressionChanged (language: string, value: CamelElement) {
if (language !== (value as any).expressionName) {
const className = CamelMetadataApi.getCamelLanguageMetadataByName(language)?.className;
value = CamelDefinitionApi.createExpression(className || '', {expression: (value as any).expression}); // perhaps copy other similar fields later
}
const exp = new ExpressionDefinition();
(exp as any)[language] = value;
- if (this.props.value) (exp as any).uuid = this.props.value.uuid;
- this.props.onExpressionChange?.call(this, this.props.property.name, exp);
- this.setState({selectIsOpen: false});
+ if (props.value) {
+ (exp as any).uuid = props.value.uuid;
+ }
+ props.onExpressionChange?.(props.property.name, exp);
+ setSelectIsOpen(false);
}
- propertyChanged = (fieldId: string, value: string | number | boolean | any) => {
- const expression = this.getExpressionValue();
+ function propertyChanged (fieldId: string, value: string | number | boolean | any) {
+ const expression = getExpressionValue();
if (expression) {
(expression as any)[fieldId] = value;
- this.expressionChanged(this.getValueLanguage(), expression);
+ expressionChanged(getValueLanguage(), expression);
}
}
- getValueClassName = (): string => {
- return CamelDefinitionApiExt.getExpressionLanguageClassName(this.props.value) || 'SimpleExpression';
+ function getValueClassName (): string {
+ return CamelDefinitionApiExt.getExpressionLanguageClassName(props.value) || 'SimpleExpression';
}
- getValueLanguage = (): string => {
- return CamelDefinitionApiExt.getExpressionLanguageName(this.props.value) || 'simple';
+ function getValueLanguage (): string {
+ return CamelDefinitionApiExt.getExpressionLanguageName(props.value) || 'simple';
}
- getExpressionValue = (): CamelElement => {
- const language = this.getValueLanguage();
- return this.props.value && (this.props.value as any)[language]
- ? (this.props.value as any)[language]
- : CamelDefinitionApi.createExpression(this.getValueClassName(), this.props.value);
+ function getExpressionValue (): CamelElement {
+ const language = getValueLanguage();
+ return props.value && (props.value as any)[language]
+ ? (props.value as any)[language]
+ : CamelDefinitionApi.createExpression(getValueClassName(), props.value);
}
- getProps = (): PropertyMeta[] => {
- const dslName = this.getValueClassName();
+ function getProps (): PropertyMeta[] {
+ const dslName = getValueClassName();
return CamelDefinitionApiExt.getElementProperties(dslName)
.filter(p => p.name !== 'id')
.filter(p => !p.isObject || (p.isObject && !CamelUi.dslHasSteps(p.type)) || (dslName === 'CatchDefinition' && p.name === 'onWhen'));
}
- render() {
- const property: PropertyMeta = this.props.property;
- const value = this.getExpressionValue();
- const dslLanguage = Languages.find((l: [string, string, string]) => l[0] === this.getValueLanguage());
- const selectOptions: JSX.Element[] = []
- Languages.forEach((lang: [string, string, string]) => {
- const s = <SelectOption key={lang[0]} value={lang[0]} description={lang[2]}/>;
- selectOptions.push(s);
- })
- return (
- <div>
- <label className="pf-v5-c-form__label" htmlFor="expression">
- <span className="pf-v5-c-form__label-text">Language</span>
- <span className="pf-v5-c-form__label-required" aria-hidden="true"> *</span>
- </label>
- <Select
- variant={SelectVariant.typeahead}
- aria-label={property.name}
- onToggle={(_event, isExpanded) => {
- this.openSelect(isExpanded)
- }}
- onSelect={(e, lang, isPlaceholder) => {
- this.expressionChanged(lang.toString(), value);
- }}
- selections={dslLanguage}
- isOpen={this.state.selectIsOpen}
- aria-labelledby={property.name}
- direction={SelectDirection.down}
- >
- {selectOptions}
- </Select>
- <FormGroup
- key={property.name}
- fieldId={property.name}
- labelIcon={property.description ?
- <Popover
- position={"left"}
- headerContent={property.displayName}
- bodyContent={property.description}>
- <button type="button" aria-label="More info" onClick={e => {
- e.preventDefault();
- e.stopPropagation();
- }}
- className="pf-v5-c-form__group-label-help">
- <HelpIcon />
- </button>
- </Popover> : <div></div>
- }>
- {value && this.getProps().map((property: PropertyMeta) =>
- <DslPropertyField key={property.name + this.props.value?.uuid} property={property}
- integration={this.props.integration}
- element={value}
- value={value ? (value as any)[property.name] : undefined}
- onExpressionChange={exp => {}}
- onParameterChange={parameter => {console.log(parameter)}}
- onDataFormatChange={dataFormat => {console.log(dataFormat)}}
- onChange={this.propertyChanged}
- dark={this.props.dark}
- dslLanguage={dslLanguage}/>
- )}
- </FormGroup>
- </div>
- )
- }
+ const property: PropertyMeta = props.property;
+ const value = getExpressionValue();
+ const dslLanguage = Languages.find((l: [string, string, string]) => l[0] === getValueLanguage());
+ const selectOptions: JSX.Element[] = []
+ Languages.forEach((lang: [string, string, string]) => {
+ const s = <SelectOption key={lang[0]} value={lang[0]} description={lang[2]}/>;
+ selectOptions.push(s);
+ })
+ return (
+ <div>
+ <label className="pf-v5-c-form__label" htmlFor="expression">
+ <span className="pf-v5-c-form__label-text">Language</span>
+ <span className="pf-v5-c-form__label-required" aria-hidden="true"> *</span>
+ </label>
+ <Select
+ variant={SelectVariant.typeahead}
+ aria-label={property.name}
+ onToggle={(_event, isExpanded) => {
+ openSelect(isExpanded)
+ }}
+ onSelect={(e, lang, isPlaceholder) => {
+ expressionChanged(lang.toString(), value);
+ }}
+ selections={dslLanguage}
+ isOpen={selectIsOpen}
+ aria-labelledby={property.name}
+ direction={SelectDirection.down}
+ >
+ {selectOptions}
+ </Select>
+ <FormGroup
+ key={property.name}
+ fieldId={property.name}
+ labelIcon={property.description ?
+ <Popover
+ position={"left"}
+ headerContent={property.displayName}
+ bodyContent={property.description}>
+ <button type="button" aria-label="More info" onClick={e => {
+ e.preventDefault();
+ e.stopPropagation();
+ }}
+ className="pf-v5-c-form__group-label-help">
+ <HelpIcon />
+ </button>
+ </Popover> : <div></div>
+ }>
+ {value && getProps().map((property: PropertyMeta) =>
+ <DslPropertyField key={property.name + props.value?.uuid}
+ property={property}
+ value={value ? (value as any)[property.name] : undefined}
+ dslLanguage={dslLanguage}
+ onExpressionChange={exp => {}}
+ onParameterChange={parameter => {}}
+ onDataFormatChange={dataFormat => {}}
+ onPropertyChange={propertyChanged}
+ />
+ )}
+ </FormGroup>
+ </div>
+ )
}
\ No newline at end of file
diff --git a/karavan-designer/src/designer/route/property/InfrastructureSelector.tsx b/karavan-designer/src/designer/route/property/InfrastructureSelector.tsx
index a9e9649f..38555892 100644
--- a/karavan-designer/src/designer/route/property/InfrastructureSelector.tsx
+++ b/karavan-designer/src/designer/route/property/InfrastructureSelector.tsx
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import React from 'react';
+import React, {useState} from 'react';
import {
Badge,
Button, capitalize, Flex, FlexItem,
@@ -32,51 +32,33 @@ interface Props {
dark: boolean,
}
-interface State {
- tabIndex: string | number
- filter?: string
- configMaps: string[]
- secrets: string[]
- services: string[]
-}
-
-export class InfrastructureSelector extends React.Component<Props, State> {
+export function InfrastructureSelector(props: Props) {
- public state: State = {
- tabIndex: "configMap",
- configMaps: InfrastructureAPI.configMaps,
- secrets: InfrastructureAPI.secrets,
- services: InfrastructureAPI.services
- };
-
- selectTab = (evt: React.MouseEvent<HTMLElement, MouseEvent>, eventKey: string | number) => {
- this.setState({tabIndex: eventKey})
- }
+ const [tabIndex, setTabIndex] = useState<string | number>("configMap");
+ const [filter, setFilter] = useState<string>();
- checkFilter = (name: string): boolean => {
- if (this.state.filter !== undefined && name) {
- return name.toLowerCase().includes(this.state.filter.toLowerCase())
+ function checkFilter (name: string): boolean {
+ if (filter !== undefined && name) {
+ return name.toLowerCase().includes(filter.toLowerCase())
} else {
return true;
}
}
- searchInput = () => {
+ function searchInput () {
return (
<Form isHorizontal className="search" autoComplete="off">
<FormGroup fieldId="search">
<TextInput className="text-field" type="text" id="search" name="search"
- value={this.state.filter}
- onChange={(_, value) => {
- this.setState({filter: value})
- }}/>
+ value={filter}
+ onChange={(_, value) => setFilter(value)}/>
</FormGroup>
</Form>
)
}
- getConfigMapTable() {
- const configMaps = this.state.configMaps;
+ function getConfigMapTable() {
+ const configMaps = InfrastructureAPI.configMaps;
return (
<Table variant='compact' borders={false}>
<Thead>
@@ -88,7 +70,7 @@ export class InfrastructureSelector extends React.Component<Props, State> {
</Thead>
<Tbody>
{configMaps
- .filter(name => this.checkFilter(name))
+ .filter(name => checkFilter(name))
.map((name, idx: number) => {
const configMapName = name.split("/")[0];
const data = name.split("/")[1];
@@ -102,7 +84,7 @@ export class InfrastructureSelector extends React.Component<Props, State> {
</Td>
<Td noPadding>
<Button style={{padding: '6px'}} variant={"link"} onClick={
- e => this.props.onSelect?.call(this, "configmap:" + name)}>
+ e => props.onSelect?.("configmap:" + name)}>
{data}
</Button>
</Td>
@@ -114,8 +96,8 @@ export class InfrastructureSelector extends React.Component<Props, State> {
)
}
- getSecretsTable() {
- const secrets = this.state.secrets;
+ function getSecretsTable() {
+ const secrets = InfrastructureAPI.secrets;
return (
<Table variant='compact' borders={false}>
<Thead>
@@ -127,7 +109,7 @@ export class InfrastructureSelector extends React.Component<Props, State> {
</Thead>
<Tbody>
{secrets
- .filter(name => this.checkFilter(name))
+ .filter(name => checkFilter(name))
.map((name, idx: number) => {
const configMapName = name.split("/")[0];
const data = name.split("/")[1];
@@ -141,7 +123,7 @@ export class InfrastructureSelector extends React.Component<Props, State> {
</Td>
<Td noPadding>
<Button style={{padding: '6px'}} variant={"link"} onClick={
- e => this.props.onSelect?.call(this, "secret:" + name)}>
+ e => props.onSelect?.("secret:" + name)}>
{data}
</Button>
</Td>
@@ -153,8 +135,8 @@ export class InfrastructureSelector extends React.Component<Props, State> {
)
}
- getServicesTable() {
- const services = this.state.services;
+ function getServicesTable() {
+ const services = InfrastructureAPI.services;
return (
<Table variant='compact' borders={false}>
<Thead>
@@ -168,7 +150,7 @@ export class InfrastructureSelector extends React.Component<Props, State> {
</Thead>
<Tbody>
{services
- .filter(name => this.checkFilter(name))
+ .filter(name => checkFilter(name))
.map((name, idx: number) => {
const serviceName = name.split("|")[0];
const hostPort = name.split("|")[1];
@@ -184,19 +166,19 @@ export class InfrastructureSelector extends React.Component<Props, State> {
{/*</Td>*/}
<Td noPadding>
<Button style={{padding: '6px'}} variant={"link"} onClick={
- e => this.props.onSelect?.call(this, hostPort)}>
+ e => props.onSelect?.(hostPort)}>
{serviceName}
</Button>
</Td>
<Td noPadding>
<Button style={{padding: '6px'}} variant={"link"} onClick={
- e => this.props.onSelect?.call(this, host)}>
+ e => props.onSelect?.(host)}>
{host}
</Button>
</Td>
<Td noPadding>
<Button style={{padding: '6px'}} variant={"link"} onClick={
- e => this.props.onSelect?.call(this, port)}>
+ e => props.onSelect?.(port)}>
{port}
</Button>
</Td>
@@ -208,37 +190,34 @@ export class InfrastructureSelector extends React.Component<Props, State> {
)
}
- render() {
- const tabIndex = this.state.tabIndex;
- const tabs = InfrastructureAPI.infrastructure === 'kubernetes' ? ['configMap', 'secret', 'services'] : ['services'];
- return (
- <Modal
- aria-label="Select from Infrastructure"
- width={'50%'}
- className='dsl-modal'
- isOpen={this.props.isOpen}
- onClose={this.props.onClose}
- header={
- <Flex direction={{default: "column"}}>
- <FlexItem>
- <h3>{"Select from " + capitalize(InfrastructureAPI.infrastructure)}</h3>
- {this.searchInput()}
- </FlexItem>
- <FlexItem>
- <Tabs style={{overflow: 'hidden'}} activeKey={this.state.tabIndex} onSelect={this.selectTab}>
- {tabs.map(tab => <Tab eventKey={tab} key={tab} title={<TabTitleText>{capitalize(tab)}</TabTitleText>} />)}
- </Tabs>
- </FlexItem>
- </Flex>
- }
- actions={{}}>
- <PageSection variant={this.props.dark ? "darker" : "light"}>
- {this.searchInput()}
- {tabIndex === 'configMap' && this.getConfigMapTable()}
- {tabIndex === 'secret' && this.getSecretsTable()}
- {tabIndex === 'services' && this.getServicesTable()}
- </PageSection>
- </Modal>
- )
- }
+ const tabs = InfrastructureAPI.infrastructure === 'kubernetes' ? ['configMap', 'secret', 'services'] : ['services'];
+ return (
+ <Modal
+ aria-label="Select from Infrastructure"
+ width={'50%'}
+ className='dsl-modal'
+ isOpen={props.isOpen}
+ onClose={props.onClose}
+ header={
+ <Flex direction={{default: "column"}}>
+ <FlexItem>
+ <h3>{"Select from " + capitalize(InfrastructureAPI.infrastructure)}</h3>
+ {searchInput()}
+ </FlexItem>
+ <FlexItem>
+ <Tabs style={{overflow: 'hidden'}} activeKey={tabIndex} onSelect={(_, eventKey) => setTabIndex(eventKey)}>
+ {tabs.map(tab => <Tab eventKey={tab} key={tab} title={<TabTitleText>{capitalize(tab)}</TabTitleText>} />)}
+ </Tabs>
+ </FlexItem>
+ </Flex>
+ }
+ actions={{}}>
+ <PageSection variant={props.dark ? "darker" : "light"}>
+ {searchInput()}
+ {tabIndex === 'configMap' && getConfigMapTable()}
+ {tabIndex === 'secret' && getSecretsTable()}
+ {tabIndex === 'services' && getServicesTable()}
+ </PageSection>
+ </Modal>
+ )
}
\ No newline at end of file
diff --git a/karavan-designer/src/designer/route/property/KameletPropertyField.tsx b/karavan-designer/src/designer/route/property/KameletPropertyField.tsx
index e837677f..69446142 100644
--- a/karavan-designer/src/designer/route/property/KameletPropertyField.tsx
+++ b/karavan-designer/src/designer/route/property/KameletPropertyField.tsx
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import React from 'react';
+import React, {useRef, useState} from 'react';
import {
FormGroup,
TextInput,
@@ -33,80 +33,66 @@ import KubernetesIcon from "@patternfly/react-icons/dist/js/icons/openshift-icon
import ShowIcon from "@patternfly/react-icons/dist/js/icons/eye-icon";
import HideIcon from "@patternfly/react-icons/dist/js/icons/eye-slash-icon";
import DockerIcon from "@patternfly/react-icons/dist/js/icons/docker-icon";
+import {usePropertiesHook} from "../usePropertiesHook";
interface Props {
property: Property,
value: any,
required: boolean,
- onParameterChange?: (parameter: string, value: string | number | boolean | any, pathParameter?: boolean) => void
}
-interface State {
- selectIsOpen: boolean
- showEditor: boolean
- showPassword: boolean
- showInfrastructureSelector: boolean
- infrastructureSelectorProperty?: string
- ref: any
-}
+export function KameletPropertyField(props: Props) {
-export class KameletPropertyField extends React.Component<Props, State> {
+ const {onParametersChange} = usePropertiesHook();
- public state: State = {
- selectIsOpen: false,
- showEditor: false,
- showPassword: false,
- showInfrastructureSelector: false,
- ref: React.createRef(),
- }
+ const [selectIsOpen, setSelectIsOpen] = useState<boolean>(false);
+ const [showEditor, setShowEditor] = useState<boolean>(false);
+ const [showPassword, setShowPassword] = useState<boolean>(false);
+ const [infrastructureSelector, setInfrastructureSelector] = useState<boolean>(false);
+ const [infrastructureSelectorProperty, setInfrastructureSelectorProperty] = useState<string | undefined>(undefined);
- openSelect = () => {
- this.setState({selectIsOpen: true});
- }
+ const ref = useRef<any>(null);
- parametersChanged = (parameter: string, value: string | number | boolean | any, pathParameter?: boolean) => {
- this.props.onParameterChange?.call(this, parameter, value, pathParameter);
- this.setState({selectIsOpen: false});
+ function parametersChanged (parameter: string, value: string | number | boolean | any, pathParameter?: boolean) {
+ onParametersChange(parameter, value, pathParameter);
+ setSelectIsOpen(false);
}
- selectInfrastructure = (value: string) => {
+ function selectInfrastructure (value: string) {
// check if there is a selection
- const textVal = this.state.ref.current;
+ const textVal = ref.current;
const cursorStart = textVal.selectionStart;
const cursorEnd = textVal.selectionEnd;
if (cursorStart !== cursorEnd){
- const prevValue = this.props.value;
+ const prevValue = props.value;
const selectedText = prevValue.substring(cursorStart, cursorEnd)
value = prevValue.replace(selectedText, value);
}
- const propertyId = this.state.infrastructureSelectorProperty;
+ const propertyId = infrastructureSelectorProperty;
if (propertyId){
if (value.startsWith("config") || value.startsWith("secret")) value = "{{" + value + "}}";
- this.parametersChanged(propertyId, value);
- this.setState({showInfrastructureSelector: false, infrastructureSelectorProperty: undefined})
+ parametersChanged(propertyId, value);
+ setInfrastructureSelector(false);
+ setInfrastructureSelectorProperty(undefined);
}
}
- openInfrastructureSelector = (propertyName: string) => {
- this.setState({infrastructureSelectorProperty: propertyName, showInfrastructureSelector: true});
- }
-
- closeInfrastructureSelector = () => {
- this.setState({showInfrastructureSelector: false})
+ function openInfrastructureSelector (propertyName: string) {
+ setInfrastructureSelector(true);
+ setInfrastructureSelectorProperty(propertyName);
}
- getInfrastructureSelectorModal() {
+ function getInfrastructureSelectorModal() {
return (
<InfrastructureSelector
dark={false}
- isOpen={this.state.showInfrastructureSelector}
- onClose={() => this.closeInfrastructureSelector()}
- onSelect={this.selectInfrastructure}/>)
+ isOpen={infrastructureSelector}
+ onClose={() => setInfrastructureSelector(false)}
+ onSelect={selectInfrastructure}/>)
}
- getStringInput() {
- const {showEditor, showPassword} = this.state;
- const {property, value} = this.props;
+ function getStringInput() {
+ const {property, value} = props;
const prefix = "parameters";
const id = prefix + "-" + property.id;
const inInfrastructure = InfrastructureAPI.infrastructure !== 'local';
@@ -116,35 +102,35 @@ export class KameletPropertyField extends React.Component<Props, State> {
return <InputGroup>
{showInfraSelectorButton &&
<Tooltip position="bottom-end" content={"Select from " + capitalize(InfrastructureAPI.infrastructure)}>
- <Button variant="control" onClick={e => this.openInfrastructureSelector(property.id)}>
+ <Button variant="control" onClick={e => openInfrastructureSelector(property.id)}>
{icon}
</Button>
</Tooltip>}
{(!showEditor || property.format === "password") &&
<TextInput
- ref={this.state.ref}
+ ref={ref}
className="text-field" isRequired
type={property.format && !showPassword ? "password" : "text"}
id={id} name={id}
value={value}
- onChange={(e, value) => this.parametersChanged(property.id, value)}/>}
+ onChange={(e, value) => parametersChanged(property.id, value)}/>}
{showEditor && property.format !== "password" &&
<TextArea autoResize={true}
className="text-field" isRequired
type="text"
id={id} name={id}
value={value}
- onChange={(e, value) => this.parametersChanged(property.id, value)}/>}
+ onChange={(e, value) => parametersChanged(property.id, value)}/>}
{property.format !== "password" &&
<Tooltip position="bottom-end" content={showEditor ? "Change to TextField" : "Change to Text Area"}>
- <Button variant="control" onClick={e => this.setState({showEditor: !showEditor})}>
+ <Button variant="control" onClick={e => setShowEditor(!showEditor)}>
{showEditor ? <CompressIcon/> : <ExpandIcon/>}
</Button>
</Tooltip>
}
{property.format === "password" &&
<Tooltip position="bottom-end" content={showPassword ? "Hide" : "Show"}>
- <Button variant="control" onClick={e => this.setState({showPassword: !showPassword})}>
+ <Button variant="control" onClick={e => setShowPassword(!showPassword)}>
{showPassword ? <ShowIcon/> : <HideIcon/>}
</Button>
</Tooltip>
@@ -152,53 +138,51 @@ export class KameletPropertyField extends React.Component<Props, State> {
</InputGroup>
}
- render() {
- const property = this.props.property;
- const value = this.props.value;
- const prefix = "parameters";
- const id = prefix + "-" + property.id;
- return (
- <div>
- <FormGroup
- key={id}
- label={property.title}
- fieldId={id}
- isRequired={this.props.required}
- labelIcon={
- <Popover
- position={"left"}
- headerContent={property.title}
- bodyContent={property.description}
- footerContent={
- <div>
- {property.default !== undefined &&
- <div>Default: {property.default.toString()}</div>}
- {property.example !== undefined && <div>Example: {property.example}</div>}
- </div>
- }>
- <button type="button" aria-label="More info" onClick={e => e.preventDefault()}
- className="pf-v5-c-form__group-label-help">
- <HelpIcon />
- </button>
- </Popover>
- }>
- {property.type === 'string' && this.getStringInput()
- }
- {['integer', 'int', 'number'].includes(property.type) &&
- <TextInput className="text-field" isRequired type='number' id={id} name={id} value={value}
- onChange={(e, value) => this.parametersChanged(property.id, Number(value))}
- />
- }
- {property.type === 'boolean' && <Switch
- id={id} name={id}
- value={value?.toString()}
- aria-label={id}
- isChecked={Boolean(value) === true}
- onChange={e => this.parametersChanged(property.id, !Boolean(value))}/>
- }
- </FormGroup>
- {this.getInfrastructureSelectorModal()}
- </div>
- )
- }
+ const property = props.property;
+ const value = props.value;
+ const prefix = "parameters";
+ const id = prefix + "-" + property.id;
+ return (
+ <div>
+ <FormGroup
+ key={id}
+ label={property.title}
+ fieldId={id}
+ isRequired={ props.required}
+ labelIcon={
+ <Popover
+ position={"left"}
+ headerContent={property.title}
+ bodyContent={property.description}
+ footerContent={
+ <div>
+ {property.default !== undefined &&
+ <div>Default: {property.default.toString()}</div>}
+ {property.example !== undefined && <div>Example: {property.example}</div>}
+ </div>
+ }>
+ <button type="button" aria-label="More info" onClick={e => e.preventDefault()}
+ className="pf-v5-c-form__group-label-help">
+ <HelpIcon />
+ </button>
+ </Popover>
+ }>
+ {property.type === 'string' && getStringInput()
+ }
+ {['integer', 'int', 'number'].includes(property.type) &&
+ <TextInput className="text-field" isRequired type='number' id={id} name={id} value={value}
+ onChange={(e, value) => parametersChanged(property.id, Number(value))}
+ />
+ }
+ {property.type === 'boolean' && <Switch
+ id={id} name={id}
+ value={value?.toString()}
+ aria-label={id}
+ isChecked={Boolean(value) === true}
+ onChange={e => parametersChanged(property.id, !Boolean(value))}/>
+ }
+ </FormGroup>
+ {getInfrastructureSelectorModal()}
+ </div>
+ )
}
\ No newline at end of file
diff --git a/karavan-designer/src/designer/route/property/ModalEditor.tsx b/karavan-designer/src/designer/route/property/ModalEditor.tsx
index ac953db3..3b18975b 100644
--- a/karavan-designer/src/designer/route/property/ModalEditor.tsx
+++ b/karavan-designer/src/designer/route/property/ModalEditor.tsx
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import React from 'react';
+import React, {useEffect, useState} from 'react';
import {
Button,
Modal,
@@ -36,64 +36,53 @@ interface Props {
showEditor: boolean
}
-interface State {
- customCode: any,
-}
-
-export class ModalEditor extends React.Component<Props, State> {
+export function ModalEditor(props: Props) {
- public state: State = {
- customCode: this.props.customCode,
- }
+ const [customCode, setCustomCode] = useState<string | undefined>();
- componentDidUpdate = (prevProps: Readonly<Props>, prevState: Readonly<State>, snapshot?: any) => {
- if (prevProps.showEditor !== this.props.showEditor) {
- this.setState({customCode: this.props.customCode})
- }
- }
+ useEffect(() => {
+ setCustomCode(props.customCode)
+ },[]);
- close(){
- this.props.onClose?.call(this);
+ function close(){
+ props.onClose();
}
- closeAndSave(){
- this.props.onSave?.call(this, this.props.property.name, this.state.customCode);
+ function closeAndSave(){
+ props.onSave(props.property.name, customCode);
}
- render() {
- const {dark, dslLanguage, title, showEditor} = this.props;
- const {customCode} = this.state;
- return (
- <Modal
- aria-label={"expression"}
- variant={ModalVariant.large}
- header={<React.Fragment>
- <Title id="modal-custom-header-label" headingLevel="h1" size={TitleSizes['2xl']}>
- {title}
- </Title>
- <p className="pf-v5-u-pt-sm">{dslLanguage?.[2]}</p>
- </React.Fragment>}
- isOpen={showEditor}
- onClose={() => this.close()}
- actions={[
- <Button key="save" variant="primary" size="sm"
- onClick={e => this.closeAndSave()}>Save</Button>,
- <Button key="cancel" variant="secondary" size="sm"
- onClick={e => this.close()}>Close</Button>
- ]}
- onEscapePress={e => this.close()}>
- <Editor
- height="400px"
- width="100%"
- defaultLanguage={'java'}
- language={'java'}
- theme={dark ? 'vs-dark' : 'light'}
- options={{lineNumbers: "off", folding: false, lineNumbersMinChars: 10, showUnused: false, fontSize: 12, minimap: {enabled: false}}}
- value={customCode?.toString()}
- className={'code-editor'}
- onChange={(value: any, ev: any) => this.setState({customCode: value})}
- />
- </Modal>
- )
- }
+ const {dark, dslLanguage, title, showEditor} = props;
+ return (
+ <Modal
+ aria-label={"expression"}
+ variant={ModalVariant.large}
+ header={<React.Fragment>
+ <Title id="modal-custom-header-label" headingLevel="h1" size={TitleSizes['2xl']}>
+ {title}
+ </Title>
+ <p className="pf-v5-u-pt-sm">{dslLanguage?.[2]}</p>
+ </React.Fragment>}
+ isOpen={showEditor}
+ onClose={() => close()}
+ actions={[
+ <Button key="save" variant="primary" size="sm"
+ onClick={e => closeAndSave()}>Save</Button>,
+ <Button key="cancel" variant="secondary" size="sm"
+ onClick={e => close()}>Close</Button>
+ ]}
+ onEscapePress={e => close()}>
+ <Editor
+ height="400px"
+ width="100%"
+ defaultLanguage={'java'}
+ language={'java'}
+ theme={dark ? 'vs-dark' : 'light'}
+ options={{lineNumbers: "off", folding: false, lineNumbersMinChars: 10, showUnused: false, fontSize: 12, minimap: {enabled: false}}}
+ value={customCode?.toString()}
+ className={'code-editor'}
+ onChange={(value,_) => setCustomCode(value)}
+ />
+ </Modal>
+ )
}
diff --git a/karavan-designer/src/designer/route/property/ObjectField.tsx b/karavan-designer/src/designer/route/property/ObjectField.tsx
index a72019b5..4d59d4f0 100644
--- a/karavan-designer/src/designer/route/property/ObjectField.tsx
+++ b/karavan-designer/src/designer/route/property/ObjectField.tsx
@@ -14,81 +14,65 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import React from 'react';
+import React, {useState} from 'react';
import '../../karavan.css';
import "@patternfly/patternfly/patternfly.css";
import {DslPropertyField} from "./DslPropertyField";
import {
ExpressionDefinition,
} from "karavan-core/lib/model/CamelDefinition";
-import {Integration, CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
+import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
import { PropertyMeta} from "karavan-core/lib/model/CamelMetadata";
interface Props {
property: PropertyMeta,
- value?: CamelElement,
- onPropertyUpdate?: (fieldId: string, value: CamelElement) => void
- integration: Integration,
+ onPropertyUpdate: (fieldId: string, value: CamelElement) => void
hideLabel?: boolean
- dark: boolean
-}
-
-interface State {
value?: CamelElement,
- selectStatus: Map<string, boolean>
}
-export class ObjectField extends React.Component<Props, State> {
- public state: State = {
- value: this.props.value,
- selectStatus: new Map<string, boolean>(),
- };
+export function ObjectField(props: Props) {
- propertyChanged = (fieldId: string, value: string | number | boolean | any) => {
- if (this.props.value) {
- const clone = CamelUtil.cloneStep(this.props.value);
+ const [value, setValue] = useState<CamelElement | undefined>(props.value);
+
+ function propertyChanged (fieldId: string, value: string | number | boolean | any) {
+ if (props.value) {
+ const clone = CamelUtil.cloneStep(props.value);
(clone as any)[fieldId] = value;
- this.setStep(clone)
- this.props.onPropertyUpdate?.call(this, this.props.property.name, clone);
+ setStep(clone)
+ props.onPropertyUpdate(props.property.name, clone);
}
}
- expressionChanged = (propertyName: string, value:ExpressionDefinition) => {
- if (this.props.value) {
- const clone = CamelUtil.cloneStep(this.props.value);
+ function expressionChanged (propertyName: string, value:ExpressionDefinition) {
+ if (props.value) {
+ const clone = CamelUtil.cloneStep(props.value);
(clone as any)[propertyName] = value;
- this.setStep(clone)
- this.props.onPropertyUpdate?.call(this, this.props.property.name, clone);
+ setStep(clone)
+ props.onPropertyUpdate(props.property.name, clone);
}
}
- setStep = (step?: CamelElement) => {
- this.setState({
- value: step,
- selectStatus: new Map<string, boolean>()
- });
+ function setStep (step?: CamelElement) {
+ setValue(step);
}
- render() {
- const value = this.props.value;
- return (
- <div className="object-field">
- {value && CamelDefinitionApiExt.getElementProperties(value.dslName).map((property: PropertyMeta) =>
- <DslPropertyField key={property.name}
- hideLabel={this.props.hideLabel}
- integration={this.props.integration}
- property={property}
- element={value}
- value={value ? (value as any)[property.name] : undefined}
- onExpressionChange={this.expressionChanged}
- onParameterChange={(parameter, value) => this.propertyChanged(property.name, value)}
- onDataFormatChange={value1 => {}}
- onChange={(fieldId, value) => this.propertyChanged(property.name, value)}
- dark={this.props.dark}/>
- )}
- </div>
- )
- }
+ const val = props.value;
+ return (
+ <div className="object-field">
+ {val && CamelDefinitionApiExt.getElementProperties(val.dslName).map((property: PropertyMeta) =>
+ <DslPropertyField key={property.name}
+ property={property}
+ element={value}
+ onExpressionChange={expressionChanged}
+ onParameterChange={(parameter, value) => propertyChanged(property.name, value)}
+ onDataFormatChange={value1 => {}}
+ onPropertyChange={(fieldId, value) => propertyChanged(property.name, value)}
+ value={val ? (val as any)[property.name] : undefined}
+ />
+ )}
+ </div>
+ )
}
\ No newline at end of file
diff --git a/karavan-designer/src/index.tsx b/karavan-designer/src/designer/route/useDrawerMutationsObserver.tsx
similarity index 51%
copy from karavan-designer/src/index.tsx
copy to karavan-designer/src/designer/route/useDrawerMutationsObserver.tsx
index 53d84ae5..45b528aa 100644
--- a/karavan-designer/src/index.tsx
+++ b/karavan-designer/src/designer/route/useDrawerMutationsObserver.tsx
@@ -14,12 +14,27 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import React from 'react';
-import "./index.css";
-import "@patternfly/patternfly/patternfly.css";
-import App from "./App";
-import {createRoot} from "react-dom/client";
-const container = document.getElementById('root');
-const root = createRoot(container!);
-root.render(<App/>);
\ No newline at end of file
+import { useLayoutEffect, useRef } from 'react';
+
+function useMutationsObserver<T extends HTMLElement>(callback: (target: T, mutations: any) => void) {
+ const ref = useRef<T>(null)
+
+ useLayoutEffect(() => {
+ const element = ref?.current;
+ if (!element) {
+ return;
+ }
+ const drawer = element.childNodes[0].childNodes[0].childNodes[1];
+ const observer2 = new MutationObserver(mutations => callback(element, mutations));
+ observer2.observe(drawer, {attributes: true, attributeOldValue: true, attributeFilter: ['style']});
+ return () => {
+ // observer1.disconnect();
+ observer2.disconnect();
+ };
+ }, [callback, ref]);
+
+ return ref
+}
+
+export default useMutationsObserver;
\ No newline at end of file
diff --git a/karavan-designer/src/designer/route/usePropertiesHook.tsx b/karavan-designer/src/designer/route/usePropertiesHook.tsx
new file mode 100644
index 00000000..058ae5f2
--- /dev/null
+++ b/karavan-designer/src/designer/route/usePropertiesHook.tsx
@@ -0,0 +1,128 @@
+/*
+ * 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 '../karavan.css';
+import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
+import {
+ DataFormatDefinition, ExpressionDefinition, ToDefinition,
+} from "karavan-core/lib/model/CamelDefinition";
+import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
+import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
+import {CamelDefinitionApi} from "karavan-core/lib/api/CamelDefinitionApi";
+import {RouteToCreate} from "../utils/CamelUi";
+import {useDesignerStore, useIntegrationStore} from "../KaravanStore";
+import {shallow} from "zustand/shallow";
+
+export function usePropertiesHook (isRouteDesigner: boolean = true) {
+
+ const [integration, setIntegration] = useIntegrationStore((state) => [state.integration, state.setIntegration], shallow)
+ const [ selectedStep, setSelectedStep, setSelectedUuids] = useDesignerStore((s) =>
+ [s.selectedStep, s.setSelectedStep, s.setSelectedUuids], shallow)
+
+ function onPropertyUpdate (element: CamelElement, newRoute?: RouteToCreate) {
+ if (isRouteDesigner) {
+ onRoutePropertyUpdate(element, newRoute);
+ } else {
+ onRestPropertyUpdate(element, newRoute);
+ }
+ }
+
+ function onRoutePropertyUpdate (element: CamelElement, newRoute?: RouteToCreate) {
+ if (newRoute) {
+ let i = CamelDefinitionApiExt.updateIntegrationRouteElement(integration, element);
+ const f = CamelDefinitionApi.createFromDefinition({uri: newRoute.componentName, parameters: {name: newRoute.name}});
+ const r = CamelDefinitionApi.createRouteDefinition({from: f, id: newRoute.name})
+ i = CamelDefinitionApiExt.addStepToIntegration(i, r, '');
+ const clone = CamelUtil.cloneIntegration(i);
+ setIntegration(clone, false);
+ setSelectedStep(element);
+ setSelectedUuids([element.uuid]);
+ } else {
+ const clone = CamelUtil.cloneIntegration(integration);
+ const i = CamelDefinitionApiExt.updateIntegrationRouteElement(clone, element);
+ setIntegration(i, true);
+ }
+ }
+
+ function onRestPropertyUpdate (element: CamelElement, newRoute?: RouteToCreate) {
+ if (newRoute) {
+ let i = CamelDefinitionApiExt.updateIntegrationRestElement(integration, element);
+ const f = CamelDefinitionApi.createFromDefinition({uri: newRoute.componentName, parameters: {name: newRoute.name}});
+ const r = CamelDefinitionApi.createRouteDefinition({from: f, id: newRoute.name})
+ i = CamelDefinitionApiExt.addStepToIntegration(i, r, '');
+ const clone = CamelUtil.cloneIntegration(i);
+ setIntegration(clone, false);
+ setSelectedStep(element);
+ } else {
+ const clone = CamelUtil.cloneIntegration(integration);
+ const i = CamelDefinitionApiExt.updateIntegrationRestElement(clone, element);
+ setIntegration(i, true);
+ // setSelectedStep(element);
+ }
+ }
+
+ function onPropertyChange (fieldId: string, value: string | number | boolean | any, newRoute?: RouteToCreate){
+ if (selectedStep) {
+ const clone = CamelUtil.cloneStep(selectedStep);
+ (clone as any)[fieldId] = value;
+ setSelectedStep(clone)
+ onPropertyUpdate(clone, newRoute);
+ }
+ }
+
+ function onDataFormatChange (value: DataFormatDefinition) {
+ value.uuid = selectedStep?.uuid ? selectedStep?.uuid : value.uuid;
+ setSelectedStep(value);
+ onPropertyUpdate(value);
+ }
+
+ function onExpressionChange (propertyName: string, exp: ExpressionDefinition) {
+ if (selectedStep) {
+ const clone = (CamelUtil.cloneStep(selectedStep));
+ (clone as any)[propertyName] = exp;
+ setSelectedStep(clone);
+ onPropertyUpdate(clone);
+ }
+ }
+
+ function onParametersChange (parameter: string, value: string | number | boolean | any, pathParameter?: boolean, newRoute?: RouteToCreate) {
+ if (selectedStep && selectedStep) {
+ const clone = (CamelUtil.cloneStep(selectedStep));
+ const parameters: any = {...(clone as any).parameters};
+ parameters[parameter] = value;
+ (clone as any).parameters = parameters;
+ setSelectedStep(clone);
+ onPropertyUpdate(clone, newRoute);
+ }
+ }
+
+ function getInternalComponentName(propertyName: string, element?: CamelElement): string {
+ if (element && element.dslName === 'ToDefinition' && propertyName === 'name') {
+ const uri: string = (element as ToDefinition).uri || '';
+ if (uri.startsWith("direct")) return "direct";
+ if (uri.startsWith("seda")) return "seda";
+ return '';
+ } else {
+ return '';
+ }
+ }
+
+ function cloneElement () {
+ // TODO:
+ }
+
+ return {cloneElement, onPropertyChange, onParametersChange, onDataFormatChange, onExpressionChange, getInternalComponentName}
+}
\ No newline at end of file
diff --git a/karavan-designer/src/index.tsx b/karavan-designer/src/designer/route/useResizeObserver.tsx
similarity index 56%
copy from karavan-designer/src/index.tsx
copy to karavan-designer/src/designer/route/useResizeObserver.tsx
index 53d84ae5..dc04401d 100644
--- a/karavan-designer/src/index.tsx
+++ b/karavan-designer/src/designer/route/useResizeObserver.tsx
@@ -14,12 +14,27 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import React from 'react';
-import "./index.css";
-import "@patternfly/patternfly/patternfly.css";
-import App from "./App";
-import {createRoot} from "react-dom/client";
-const container = document.getElementById('root');
-const root = createRoot(container!);
-root.render(<App/>);
\ No newline at end of file
+import { useLayoutEffect, useRef } from 'react';
+
+function useResizeObserver<T extends HTMLElement>(callback: (target: T, entry: ResizeObserverEntry) => void) {
+ const ref = useRef<T>(null)
+
+ useLayoutEffect(() => {
+ const element = ref?.current;
+ if (!element) {
+ return;
+ }
+ const observer1 = new ResizeObserver((entries) => {
+ callback(element, entries[0]);
+ });
+ observer1.observe(element);
+ return () => {
+ observer1.disconnect();
+ };
+ }, [callback, ref]);
+
+ return ref
+}
+
+export default useResizeObserver;
\ No newline at end of file
diff --git a/karavan-designer/src/designer/route/useRouteDesignerHook.tsx b/karavan-designer/src/designer/route/useRouteDesignerHook.tsx
new file mode 100644
index 00000000..28dceaca
--- /dev/null
+++ b/karavan-designer/src/designer/route/useRouteDesignerHook.tsx
@@ -0,0 +1,302 @@
+/*
+ * 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 from 'react';
+import '../karavan.css';
+import {DslMetaModel} from "../utils/DslMetaModel";
+import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
+import {ChoiceDefinition, FromDefinition, LogDefinition, RouteConfigurationDefinition, RouteDefinition} from "karavan-core/lib/model/CamelDefinition";
+import {CamelElement, Integration} from "karavan-core/lib/model/IntegrationDefinition";
+import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
+import {CamelDefinitionApi} from "karavan-core/lib/api/CamelDefinitionApi";
+import {Command, EventBus} from "../utils/EventBus";
+import {RouteToCreate} from "../utils/CamelUi";
+import {CamelDisplayUtil} from "karavan-core/lib/api/CamelDisplayUtil";
+import {toPng} from 'html-to-image';
+import {RouteDesigner} from "./RouteDesigner";
+import {findDOMNode} from "react-dom";
+import {Subscription} from "rxjs";
+import debounce from 'lodash.debounce';
+import {useDesignerStore, useIntegrationStore, useSelectorStore} from "../KaravanStore";
+import {shallow} from "zustand/shallow";
+
+export function useRouteDesignerHook () {
+
+ const [integration, setIntegration] = useIntegrationStore((state) => [state.integration, state.setIntegration], shallow)
+ const [selectedUuids,clipboardSteps,shiftKeyPressed,
+ setShowDeleteConfirmation, setDeleteMessage, setSelectedStep, setSelectedUuids, setClipboardSteps, setShiftKeyPressed,
+ width, height, dark, hideLogDSL] = useDesignerStore((s) =>
+ [s.selectedUuids,s.clipboardSteps, s.shiftKeyPressed,
+ s.setShowDeleteConfirmation, s.setDeleteMessage, s.setSelectedStep, s.setSelectedUuids, s.setClipboardSteps, s.setShiftKeyPressed,
+ s.width, s.height, s.dark, s.hideLogDSL], shallow)
+ const [setParentId, setShowSelector, setSelectorTabIndex, setParentDsl, setShowSteps, setSelectedPosition] = useSelectorStore((s) =>
+ [s.setParentId, s.setShowSelector, s.setSelectorTabIndex, s.setParentDsl, s.setShowSteps, s.setSelectedPosition], shallow)
+
+ function onCommand (command: Command, printerRef: React.MutableRefObject<HTMLDivElement | null>) {
+ switch (command.command){
+ case "downloadImage": integrationImageDownload(printerRef);
+ }
+ }
+
+ const onShowDeleteConfirmation = (id: string) => {
+ let message: string;
+ const uuidsToDelete:string [] = [id];
+ let ce: CamelElement;
+ ce = CamelDefinitionApiExt.findElementInIntegration(integration, id)!;
+ if (ce.dslName === 'FromDefinition') { // Get the RouteDefinition for this.routeDesigner. Use its uuid.
+ let flows = integration.spec.flows!;
+ for (let i = 0; i < flows.length; i++) {
+ if (flows[i].dslName === 'RouteDefinition') {
+ let routeDefinition: RouteDefinition = flows[i];
+ if (routeDefinition.from.uuid === id) {
+ uuidsToDelete.push(routeDefinition.uuid);
+ break;
+ }
+ }
+ }
+ message = 'Deleting the first element will delete the entire route!';
+ } else if (ce.dslName === 'RouteDefinition') {
+ message = 'Delete route?';
+ } else if (ce.dslName === 'RouteConfigurationDefinition') {
+ message = 'Delete route configuration?';
+ } else {
+ message = 'Delete element from route?';
+ }
+ setShowDeleteConfirmation(true);
+ setDeleteMessage(message);
+ setSelectedUuids(uuidsToDelete);
+ }
+
+ const deleteElement = () => {
+ selectedUuids.forEach(uuidToDelete => {
+ const i = CamelDefinitionApiExt.deleteStepFromIntegration(integration, uuidToDelete);
+ setIntegration(i, false);
+ setShowSelector(false);
+ setShowDeleteConfirmation(false);
+ setDeleteMessage('');
+ setSelectedStep(undefined);
+ setSelectedUuids([uuidToDelete]);
+
+ const el = new CamelElement("");
+ el.uuid = uuidToDelete;
+ EventBus.sendPosition("delete", el, undefined, new DOMRect(), new DOMRect(), 0);
+ });
+ }
+
+ const selectElement = (element: CamelElement) => {
+ const uuids = [...selectedUuids];
+ let canNotAdd: boolean = false;
+ if (shiftKeyPressed) {
+ const hasFrom = uuids.map(e => CamelDefinitionApiExt.findElementInIntegration(integration, e)?.dslName === 'FromDefinition').filter(r => r).length > 0;
+ canNotAdd = hasFrom || (uuids.length > 0 && element.dslName === 'FromDefinition');
+ }
+ const add = shiftKeyPressed && !uuids.includes(element.uuid);
+ const remove = shiftKeyPressed && uuids.includes(element.uuid);
+ // TODO: do we need to change Integration just for select????
+ const i = CamelDisplayUtil.setIntegrationVisibility(integration, element.uuid);
+
+ if (remove) {
+ const index = uuids.indexOf(element.uuid);
+ uuids.splice(index, 1);
+ } else if (add && !canNotAdd) {
+ uuids.push(element.uuid);
+ }
+ const uuid: string = uuids.includes(element.uuid) ? element.uuid : uuids.at(0) || '';
+ const selectedElement = shiftKeyPressed ? CamelDefinitionApiExt.findElementInIntegration(integration, uuid) : element;
+
+ setIntegration(i, true);
+ setSelectedStep(selectedElement);
+ setSelectedUuids(shiftKeyPressed ? [...uuids] : [element.uuid])
+ }
+
+ function handleKeyDown (event: KeyboardEvent) {
+ if ((event.shiftKey)) {
+ setShiftKeyPressed(true);
+ }
+ if (window.document.hasFocus() && window.document.activeElement) {
+ if (['BODY', 'MAIN'].includes(window.document.activeElement.tagName)) {
+ let charCode = String.fromCharCode(event.which).toLowerCase();
+ if ((event.ctrlKey || event.metaKey) && charCode === 'c') {
+ copyToClipboard();
+ } else if ((event.ctrlKey || event.metaKey) && charCode === 'v') {
+ pasteFromClipboard();
+ }
+ }
+ } else {
+ if (event.repeat) {
+ window.dispatchEvent(event);
+ }
+ }
+ }
+
+ function handleKeyUp (event: KeyboardEvent) {
+ setShiftKeyPressed(false);
+ if (event.repeat) {
+ window.dispatchEvent(event);
+ }
+ }
+
+ function copyToClipboard (): void {
+ const steps: CamelElement[] = []
+ selectedUuids.forEach(selectedUuid => {
+ const selectedElement = CamelDefinitionApiExt.findElementInIntegration(integration, selectedUuid);
+ if (selectedElement) {
+ steps.push(selectedElement);
+ }
+ })
+ if (steps.length > 0) {
+ setClipboardSteps(steps);
+ }
+ }
+ function pasteFromClipboard (): void {
+ if (clipboardSteps.length === 1 && clipboardSteps[0]?.dslName === 'FromDefinition') {
+ const clone = CamelUtil.cloneStep(clipboardSteps[0], true);
+ const route = CamelDefinitionApi.createRouteDefinition({from: clone});
+ addStep(route, '', 0)
+ } else if (clipboardSteps.length === 1 && clipboardSteps[0]?.dslName === 'RouteDefinition') {
+ const clone = CamelUtil.cloneStep(clipboardSteps[0], true);
+ addStep(clone, '', 0)
+ } else if (selectedUuids.length === 1) {
+ const targetMeta = CamelDefinitionApiExt.findElementMetaInIntegration(integration, selectedUuids[0]);
+ clipboardSteps.reverse().forEach(clipboardStep => {
+ if (clipboardStep && targetMeta.parentUuid) {
+ const clone = CamelUtil.cloneStep(clipboardStep, true);
+ addStep(clone, targetMeta.parentUuid, targetMeta.position);
+ }
+ })
+ }
+ }
+
+ function unselectElement (evt: React.MouseEvent<HTMLDivElement, MouseEvent>) {
+ if ((evt.target as any).dataset.click === 'FLOWS') {
+ evt.stopPropagation()
+ const i = CamelDisplayUtil.setIntegrationVisibility(integration, undefined);
+ setIntegration(i, true);
+ setSelectedStep(undefined);
+ setSelectedPosition(undefined);
+ setSelectedUuids([]);
+ }
+ }
+
+ const openSelector = (parentId: string | undefined, parentDsl: string | undefined, showSteps: boolean = true, position?: number | undefined, selectorTabIndex?: string | number) => {
+ setShowSelector(true);
+ setParentId(parentId || '');
+ setParentDsl(parentDsl);
+ setShowSteps(showSteps);
+ setSelectedPosition(position);
+ setSelectorTabIndex((parentId === undefined && parentDsl === undefined) ? 'kamelet' : 'eip');
+ }
+
+ const onDslSelect = (dsl: DslMetaModel, parentId: string, position?: number | undefined) => {
+ switch (dsl.dsl) {
+ case 'FromDefinition' :
+ const route = CamelDefinitionApi.createRouteDefinition({from: new FromDefinition({uri: dsl.uri})});
+ addStep(route, parentId, position)
+ break;
+ case 'ToDefinition' :
+ const to = CamelDefinitionApi.createStep(dsl.dsl, {uri: dsl.uri});
+ addStep(to, parentId, position)
+ break;
+ case 'ToDynamicDefinition' :
+ const toD = CamelDefinitionApi.createStep(dsl.dsl, {uri: dsl.uri});
+ addStep(toD, parentId, position)
+ break;
+ case 'KameletDefinition' :
+ const kamelet = CamelDefinitionApi.createStep(dsl.dsl, {name: dsl.name});
+ addStep(kamelet, parentId, position)
+ break;
+ default:
+ const step = CamelDefinitionApi.createStep(dsl.dsl, undefined);
+ const augmentedStep = setDslDefaults(step);
+ addStep(augmentedStep, parentId, position)
+ break;
+ }
+ }
+
+ function setDslDefaults(step: CamelElement): CamelElement {
+ if (step.dslName === 'LogDefinition') {
+ // eslint-disable-next-line no-template-curly-in-string
+ (step as LogDefinition).message = "${body}";
+ }
+ if (step.dslName === 'ChoiceDefinition') {
+ (step as ChoiceDefinition).when?.push(CamelDefinitionApi.createStep('WhenDefinition', undefined));
+ (step as ChoiceDefinition).otherwise = CamelDefinitionApi.createStep('OtherwiseDefinition', undefined);
+ }
+ return step;
+ }
+
+ const createRouteConfiguration = () => {
+ const clone = CamelUtil.cloneIntegration(integration);
+ const routeConfiguration = new RouteConfigurationDefinition();
+ const i = CamelDefinitionApiExt.addRouteConfigurationToIntegration(clone, routeConfiguration);
+ setIntegration(i, false);
+ setSelectedStep(routeConfiguration);
+ setSelectedUuids([routeConfiguration.uuid]);
+ }
+
+ const addStep = (step: CamelElement, parentId: string, position?: number | undefined) => {
+ const clone = CamelUtil.cloneIntegration(integration);
+ const i = CamelDefinitionApiExt.addStepToIntegration(clone, step, parentId, position);
+ const selectedStep = step.dslName === 'RouteDefinition' ? (step as RouteDefinition).from : step;
+ setIntegration(i, false);
+ setSelectedStep(selectedStep);
+ setSelectedUuids([selectedStep.uuid]);
+ }
+
+
+ const moveElement = (source: string, target: string, asChild: boolean) => {
+ const i = CamelDefinitionApiExt.moveRouteElement(integration, source, target, asChild);
+ const clone = CamelUtil.cloneIntegration(i);
+ const selectedStep = CamelDefinitionApiExt.findElementInIntegration(clone, source);
+
+ setIntegration(clone, false);
+ setShowSelector(false);
+ setSelectedStep(selectedStep);
+ setSelectedUuids([source]);
+ }
+
+ function downloadIntegrationImage(dataUrl: string) {
+ const a = document.createElement('a');
+ a.setAttribute('download', 'karavan-routes.png');
+ a.setAttribute('href', dataUrl);
+ a.click();
+ }
+
+ function integrationImageDownloadFilter (node: HTMLElement) {
+ const exclusionClasses = ['add-flow'];
+ return !exclusionClasses.some(classname => {
+ return node.classList === undefined ? false : node.classList.contains(classname);
+ });
+ }
+
+ function integrationImageDownload(printerRef: React.MutableRefObject<HTMLDivElement | null>) {
+ const ref = printerRef.current;
+ if (ref !== null) {
+ toPng(ref, {
+ style: {overflow: 'hidden'}, cacheBust: true, filter: integrationImageDownloadFilter,
+ height: height, width: width, backgroundColor: dark ? "black" : "white"
+ }).then(v => {
+ toPng(ref, {
+ style: {overflow: 'hidden'}, cacheBust: true, filter: integrationImageDownloadFilter,
+ height: height, width: width, backgroundColor: dark ? "black" : "white"
+ }).then(downloadIntegrationImage);
+ })
+ }
+ }
+
+ return { deleteElement, selectElement, moveElement, onShowDeleteConfirmation, onDslSelect, openSelector,
+ createRouteConfiguration, onCommand, handleKeyDown, handleKeyUp, unselectElement}
+}
\ No newline at end of file
diff --git a/karavan-designer/src/designer/templates/TemplatesDesigner.tsx b/karavan-designer/src/designer/templates/TemplatesDesigner.tsx
deleted file mode 100644
index ffc5d9d9..00000000
--- a/karavan-designer/src/designer/templates/TemplatesDesigner.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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 from 'react';
-import {
- EmptyState, EmptyStateBody, EmptyStateIcon,
- PageSection, EmptyStateHeader,
-} from '@patternfly/react-core';
-import '../karavan.css';
-import {CamelElement, Integration} from "karavan-core/lib/model/IntegrationDefinition";
-import CubesIcon from '@patternfly/react-icons/dist/esm/icons/cubes-icon';
-
-interface Props {
- onSave?: (integration: Integration, propertyOnly: boolean) => void
- integration: Integration
- dark: boolean
-}
-
-interface State {
- integration: Integration
- selectedStep?: CamelElement
- key: string
- propertyOnly: boolean
-}
-
-export class TemplatesDesigner extends React.Component<Props, State> {
-
- public state: State = {
- integration: this.props.integration,
- key: "",
- propertyOnly: false
- };
-
- componentDidUpdate = (prevProps: Readonly<Props>, prevState: Readonly<State>, snapshot?: any) => {
- if (prevState.key !== this.state.key) {
- this.props.onSave?.call(this, this.state.integration, this.state.propertyOnly);
- }
- }
-
- onIntegrationUpdate = (i: Integration) => {
- this.setState({integration: i, key: Math.random().toString()});
- }
-
- render() {
- return (
- <PageSection className="templates-page" isFilled padding={{default: 'noPadding'}}>
- <div className="templates-page-columns">
- <EmptyState>
- <EmptyStateHeader titleText="Templates" icon={<EmptyStateIcon icon={CubesIcon} />} headingLevel="h4" />
- <EmptyStateBody>
- Templates not implemented yet
- </EmptyStateBody>
- </EmptyState>
- </div>
- </PageSection>
- );
- }
-}
diff --git a/karavan-designer/src/designer/utils/CamelUi.tsx b/karavan-designer/src/designer/utils/CamelUi.tsx
index 8d4157c4..c5d63801 100644
--- a/karavan-designer/src/designer/utils/CamelUi.tsx
+++ b/karavan-designer/src/designer/utils/CamelUi.tsx
@@ -81,7 +81,7 @@ import {
SplitIcon,
SpringIcon,
TerminalIcon,
- TestingIcon,
+ TestingIcon, ToIcon,
TransformationIcon,
ValidationIcon,
WebserviceIcon,
@@ -161,6 +161,16 @@ const connectorNavs = ['routing', "transformation", "error", "configuration", "e
export class CamelUi {
+ static createNewInternalRoute = (uri: string): RouteToCreate | undefined => {
+ const uris = uri.toString().split(":");
+ const componentName = uris[0];
+ const name = uris[1];
+ if (['direct', 'seda'].includes(componentName)) {
+ return new RouteToCreate(componentName, name)
+ }
+ return undefined;
+ }
+
static getSelectorModelTypes = (parentDsl: string | undefined, showSteps: boolean = true, filter: string | undefined = undefined): [string, number][] => {
const navs = CamelUi.getSelectorModelsForParent(parentDsl, showSteps).map(dsl => dsl.navigation.split(","))
.reduce((accumulator, value) => accumulator.concat(value), [])
@@ -705,6 +715,8 @@ export class CamelUi {
switch (dslName) {
case 'AggregateDefinition':
return <AggregateIcon/>;
+ case 'ToDefinition':
+ return <ToIcon/>;
case 'ChoiceDefinition' :
return <ChoiceIcon/>;
case 'SplitDefinition' :
@@ -723,6 +735,18 @@ export class CamelUi {
return <InterceptFrom/>;
case 'InterceptSendToEndpointDefinition' :
return <InterceptSendToEndpoint/>;
+ case 'GetDefinition' :
+ return <ApiIcon/>;
+ case 'PostDefinition' :
+ return <ApiIcon/>;
+ case 'PutDefinition' :
+ return <ApiIcon/>;
+ case 'PatchDefinition' :
+ return <ApiIcon/>;
+ case 'DeleteDefinition' :
+ return <ApiIcon/>;
+ case 'HeadDefinition' :
+ return <ApiIcon/>;
default:
return this.getIconFromSource(CamelUi.getIconSrcForName(dslName))
}
diff --git a/karavan-designer/src/designer/utils/EventBus.ts b/karavan-designer/src/designer/utils/EventBus.ts
index aeae6ff7..ec11019e 100644
--- a/karavan-designer/src/designer/utils/EventBus.ts
+++ b/karavan-designer/src/designer/utils/EventBus.ts
@@ -15,7 +15,7 @@
* limitations under the License.
*/
import {Subject} from 'rxjs';
-import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
+import {CamelElement, Integration} from "karavan-core/lib/model/IntegrationDefinition";
const positions = new Subject<DslPosition>();
@@ -59,6 +59,17 @@ export class Command {
}
}
+const updates = new Subject<IntegrationUpdate>();
+export class IntegrationUpdate {
+ integration: Integration;
+ propertyOnly: boolean;
+
+ constructor(integration: Integration, propertyOnly: boolean) {
+ this.integration = integration;
+ this.propertyOnly = propertyOnly;
+ }
+}
+
export const EventBus = {
sendPosition: (command: "add" | "delete" | "clean",
step: CamelElement,
@@ -70,6 +81,9 @@ export const EventBus = {
isSelected: boolean = false) => positions.next(new DslPosition(command, step, parent, rect, headerRect, position, inSteps, isSelected)),
onPosition: () => positions.asObservable(),
+ sendIntegrationUpdate: (i: Integration, propertyOnly: boolean) => updates.next(new IntegrationUpdate(i, propertyOnly)),
+ onIntegrationUpdate: () => updates.asObservable(),
+
sendCommand: (command: string, data?: any) => commands.next(new Command(command, data)),
onCommand: () => commands.asObservable(),
}
diff --git a/karavan-designer/src/designer/utils/InfrastructureAPI.ts b/karavan-designer/src/designer/utils/InfrastructureAPI.ts
index 8b182dc1..f0d8faef 100644
--- a/karavan-designer/src/designer/utils/InfrastructureAPI.ts
+++ b/karavan-designer/src/designer/utils/InfrastructureAPI.ts
@@ -1,5 +1,39 @@
+/*
+ * 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.
+ */
export class InfrastructureAPI {
+ // code API
+ static onGetCustomCode: (name: string, javaType: string) => Promise<string | undefined>;
+ static onSaveCustomCode: (name: string, code: string) => void;
+ static onSave: (filename: string, yaml: string, propertyOnly: boolean) => void;
+
+ static setOnGetCustomCode(onGetCustomCode: (name: string, javaType: string) => Promise<string | undefined>){
+ this.onGetCustomCode = onGetCustomCode
+ }
+
+ static setOnSaveCustomCode(onSaveCustomCode: (name: string, code: string) => void){
+ this.onSaveCustomCode = onSaveCustomCode
+ }
+
+ static setOnSave(onSave:(filename: string, yaml: string, propertyOnly: boolean) => void){
+ this.onSave = onSave
+ }
+
+ // Kubernetes/Docker API
static infrastructure: 'kubernetes' | 'docker' | 'local' = 'local';
static configMaps: string[] = [];
static secrets: string[] = [];
diff --git a/karavan-designer/src/designer/utils/IntegrationHeader.tsx b/karavan-designer/src/designer/utils/IntegrationHeader.tsx
new file mode 100644
index 00000000..c117d434
--- /dev/null
+++ b/karavan-designer/src/designer/utils/IntegrationHeader.tsx
@@ -0,0 +1,41 @@
+/*
+ * 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 from 'react';
+import {FormGroup, TextInput, Title} from "@patternfly/react-core";
+import {useIntegrationStore} from "../KaravanStore";
+import {shallow} from "zustand/shallow";
+
+export function IntegrationHeader () {
+
+ const [integration] = useIntegrationStore((state) => [state.integration], shallow)
+
+ return (
+ <div className="headers">
+ <Title headingLevel="h1" size="md">Integration</Title>
+ {/*<FormGroup label="Title" fieldId="title" isRequired>*/}
+ {/* <TextInput className="text-field" type="text" id="title" name="title" isReadOnly*/}
+ {/* value={*/}
+ {/* CamelUi.titleFromName(this.props.integration.metadata.name)*/}
+ {/* }/>*/}
+ {/*</FormGroup>*/}
+ <FormGroup label="Name" fieldId="name" isRequired>
+ <TextInput className="text-field" type="text" id="name" name="name"
+ value={integration.metadata.name} readOnlyVariant="default"/>
+ </FormGroup>
+ </div>
+ )
+}
diff --git a/karavan-designer/src/designer/utils/KaravanComponents.tsx b/karavan-designer/src/designer/utils/KaravanComponents.tsx
deleted file mode 100644
index faddd84d..00000000
--- a/karavan-designer/src/designer/utils/KaravanComponents.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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 from 'react';
-import {FormGroup, TextInput, Title} from "@patternfly/react-core";
-import {Integration} from "karavan-core/lib/model/IntegrationDefinition";
-
-interface Props {
- integration: Integration,
-}
-
-export class IntegrationHeader extends React.Component<Props> {
-
- render() {
- return (
- <div className="headers">
- <Title headingLevel="h1" size="md">Integration</Title>
- {/*<FormGroup label="Title" fieldId="title" isRequired>*/}
- {/* <TextInput className="text-field" type="text" id="title" name="title" isReadOnly*/}
- {/* value={*/}
- {/* CamelUi.titleFromName(this.props.integration.metadata.name)*/}
- {/* }/>*/}
- {/*</FormGroup>*/}
- <FormGroup label="Name" fieldId="name" isRequired>
- <TextInput className="text-field" type="text" id="name" name="name"
- value={this.props.integration.metadata.name} readOnlyVariant="default"/>
- </FormGroup>
- </div>
- )
- }
-}
diff --git a/karavan-designer/src/designer/utils/KaravanIcons.tsx b/karavan-designer/src/designer/utils/KaravanIcons.tsx
index b7228640..17560ff4 100644
--- a/karavan-designer/src/designer/utils/KaravanIcons.tsx
+++ b/karavan-designer/src/designer/utils/KaravanIcons.tsx
@@ -495,6 +495,28 @@ export function AggregateIcon() {
);
}
+export function ToIcon() {
+ return (
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ width={800}
+ height={800}
+ viewBox="0 0 32 32"
+ className="icon"
+ >
+ <path
+ d="M24 22v-1h6v-2h-6v-6h6v-2h-6v-1a2 2 0 0 0-2-2h-6a8.007 8.007 0 0 0-7.93 7v2A8.007 8.007 0 0 0 16 24h6a2 2 0 0 0 2-2zm-8 0a6 6 0 1 1 0-12h6v12z"/>
+ <path
+ d="M0 0h32v32H0z"
+ data-name="<Transparent Rectangle>"
+ style={{
+ fill: "none",
+ }}
+ />
+ </svg>
+ );
+}
+
export function ChoiceIcon() {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 700" className="icon">
@@ -794,6 +816,7 @@ export function MessagingIcon() {
</svg>
);
}
+
export function SchedulingIcon() {
return (
<svg
@@ -802,8 +825,8 @@ export function SchedulingIcon() {
height={800}
viewBox="0 0 32 32"
className="icon">
- <path d="M16 30a14 14 0 1 1 14-14 14 14 0 0 1-14 14Zm0-26a12 12 0 1 0 12 12A12 12 0 0 0 16 4Z" />
- <path d="M20.59 22 15 16.41V7h2v8.58l5 5.01L20.59 22z" />
+ <path d="M16 30a14 14 0 1 1 14-14 14 14 0 0 1-14 14Zm0-26a12 12 0 1 0 12 12A12 12 0 0 0 16 4Z"/>
+ <path d="M20.59 22 15 16.41V7h2v8.58l5 5.01L20.59 22z"/>
<path
d="M0 0h32v32H0z"
data-name="<Transparent Rectangle>"
@@ -823,7 +846,8 @@ export function HttpIcon() {
height={800}
viewBox="0 0 32 32"
className="icon">
- <path d="M30 11h-5v10h2v-3h3a2.003 2.003 0 0 0 2-2v-3a2.002 2.002 0 0 0-2-2Zm-3 5v-3h3l.001 3ZM10 13h2v8h2v-8h2v-2h-6v2zM23 11h-6v2h2v8h2v-8h2v-2zM6 11v4H3v-4H1v10h2v-4h3v4h2V11H6z" />
+ <path
+ d="M30 11h-5v10h2v-3h3a2.003 2.003 0 0 0 2-2v-3a2.002 2.002 0 0 0-2-2Zm-3 5v-3h3l.001 3ZM10 13h2v8h2v-8h2v-2h-6v2zM23 11h-6v2h2v8h2v-8h2v-2zM6 11v4H3v-4H1v10h2v-4h3v4h2V11H6z"/>
<path
d="M0 0h32v32H0z"
data-name="<Transparent Rectangle>"
@@ -834,6 +858,7 @@ export function HttpIcon() {
</svg>
);
}
+
export function GoogleCloudIcon() {
return (
<svg
@@ -862,6 +887,7 @@ export function GoogleCloudIcon() {
</svg>
);
}
+
export function AwsIcon() {
return (
<svg
@@ -876,12 +902,15 @@ export function AwsIcon() {
d="M4.51 7.687c0 .197.02.357.058.475.042.117.096.245.17.384a.233.233 0 0 1 .037.123c0 .053-.032.107-.1.16l-.336.224a.255.255 0 0 1-.138.048c-.054 0-.107-.026-.16-.074a1.652 1.652 0 0 1-.192-.251 4.137 4.137 0 0 1-.165-.315c-.415.491-.936.737-1.564.737-.447 0-.804-.129-1.064-.385-.261-.256-.394-.598-.394-1.025 0-.454.16-.822.484-1.1.325-.278.756-.416 1.304-.416.18 0 .367.016.564.042.197.027.4.07.612.118v-.39c0-.406-.085-.689-.25-.854-.17-.166-.458-.246-.868-.246-.186 0-.37 [...]
/>
<g fill="#F90" fillRule="evenodd" clipRule="evenodd">
- <path d="M14.465 11.813c-1.75 1.297-4.294 1.986-6.481 1.986-3.065 0-5.827-1.137-7.913-3.027-.165-.15-.016-.353.18-.235 2.257 1.313 5.04 2.109 7.92 2.109 1.941 0 4.075-.406 6.039-1.239.293-.133.543.192.255.406z" />
- <path d="M15.194 10.98c-.223-.287-1.479-.138-2.048-.069-.17.022-.197-.128-.043-.24 1-.705 2.645-.502 2.836-.267.192.24-.053 1.89-.99 2.68-.143.123-.281.06-.218-.1.213-.53.687-1.72.463-2.003z" />
+ <path
+ d="M14.465 11.813c-1.75 1.297-4.294 1.986-6.481 1.986-3.065 0-5.827-1.137-7.913-3.027-.165-.15-.016-.353.18-.235 2.257 1.313 5.04 2.109 7.92 2.109 1.941 0 4.075-.406 6.039-1.239.293-.133.543.192.255.406z"/>
+ <path
+ d="M15.194 10.98c-.223-.287-1.479-.138-2.048-.069-.17.022-.197-.128-.043-.24 1-.705 2.645-.502 2.836-.267.192.24-.053 1.89-.99 2.68-.143.123-.281.06-.218-.1.213-.53.687-1.72.463-2.003z"/>
</g>
</svg>
);
}
+
export function MailIcon() {
return (
<svg
@@ -891,7 +920,8 @@ export function MailIcon() {
viewBox="0 0 32 32"
className="icon">
<title>{"email"}</title>
- <path d="M28 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2Zm-2.2 2L16 14.78 6.2 8ZM4 24V8.91l11.43 7.91a1 1 0 0 0 1.14 0L28 8.91V24Z" />
+ <path
+ d="M28 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2Zm-2.2 2L16 14.78 6.2 8ZM4 24V8.91l11.43 7.91a1 1 0 0 0 1.14 0L28 8.91V24Z"/>
<path
d="M0 0h32v32H0z"
data-name="<Transparent Rectangle>"
@@ -902,6 +932,7 @@ export function MailIcon() {
</svg>
);
}
+
export function IotIcon() {
return (
<svg
@@ -911,9 +942,10 @@ export function IotIcon() {
viewBox="0 0 32 32"
className="icon">
<title>{"iot--platform"}</title>
- <path d="M30 19h-4v-4h-2v9H8V8h9V6h-4V2h-2v4H8a2.002 2.002 0 0 0-2 2v3H2v2h4v6H2v2h4v3a2.002 2.002 0 0 0 2 2h3v4h2v-4h6v4h2v-4h3a2.003 2.003 0 0 0 2-2v-3h4Z" />
- <path d="M21 21H11V11h10Zm-8-2h6v-6h-6ZM31 13h-2A10.012 10.012 0 0 0 19 3V1a12.013 12.013 0 0 1 12 12Z" />
- <path d="M26 13h-2a5.006 5.006 0 0 0-5-5V6a7.008 7.008 0 0 1 7 7Z" />
+ <path
+ d="M30 19h-4v-4h-2v9H8V8h9V6h-4V2h-2v4H8a2.002 2.002 0 0 0-2 2v3H2v2h4v6H2v2h4v3a2.002 2.002 0 0 0 2 2h3v4h2v-4h6v4h2v-4h3a2.003 2.003 0 0 0 2-2v-3h4Z"/>
+ <path d="M21 21H11V11h10Zm-8-2h6v-6h-6ZM31 13h-2A10.012 10.012 0 0 0 19 3V1a12.013 12.013 0 0 1 12 12Z"/>
+ <path d="M26 13h-2a5.006 5.006 0 0 0-5-5V6a7.008 7.008 0 0 1 7 7Z"/>
<path
d="M0 0h32v32H0z"
data-name="<Transparent Rectangle>"
@@ -924,6 +956,7 @@ export function IotIcon() {
</svg>
);
}
+
export function GithubIcon() {
return (
<svg
@@ -933,10 +966,13 @@ export function GithubIcon() {
data-view-component="true"
viewBox="0 0 16 16"
className="icon">
- <path d="M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16. [...]
+ <path
+ d="M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45 [...]
</svg>
);
-}export function CassandraIcon() {
+}
+
+export function CassandraIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -946,7 +982,7 @@ export function GithubIcon() {
viewBox="0 0 113.63554 58.899029"
className="icon">
<defs>
- <path id="a" d="M24.216.082v24.141H.053V.082z" />
+ <path id="a" d="M24.216.082v24.141H.053V.082z"/>
</defs>
<g
style={{
@@ -998,7 +1034,7 @@ export function GithubIcon() {
/>
<g transform="translate(43.304 10.642)">
<mask id="b" fill="#fff">
- <use xlinkHref="#a" />
+ <use xlinkHref="#a"/>
</mask>
<path
fill="#fff"
@@ -1026,6 +1062,7 @@ export function GithubIcon() {
</svg>
);
}
+
export function ActivemqIcon() {
return (
<svg
@@ -1042,16 +1079,16 @@ export function ActivemqIcon() {
x={-0.017}
y={-0.011}
>
- <feFlood floodColor="#000" floodOpacity={0.498} result="flood" />
+ <feFlood floodColor="#000" floodOpacity={0.498} result="flood"/>
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
/>
- <feGaussianBlur in="composite1" result="blur" stdDeviation={0.2} />
- <feOffset dx={1} dy={1} result="offset" />
- <feComposite in="SourceGraphic" in2="offset" result="composite2" />
+ <feGaussianBlur in="composite1" result="blur" stdDeviation={0.2}/>
+ <feOffset dx={1} dy={1} result="offset"/>
+ <feComposite in="SourceGraphic" in2="offset" result="composite2"/>
</filter>
</defs>
<g
@@ -1313,6 +1350,7 @@ export function ActivemqIcon() {
</svg>
);
}
+
export function KafkaIcon() {
return (
<svg
@@ -1329,6 +1367,7 @@ export function KafkaIcon() {
</svg>
);
}
+
export function GrapeIcon() {
return (
<svg
@@ -1363,6 +1402,7 @@ export function GrapeIcon() {
</svg>
);
}
+
export function MachineLearningIcon() {
return (
<svg
@@ -1371,8 +1411,10 @@ export function MachineLearningIcon() {
height={800}
viewBox="0 0 32 32"
className="icon">
- <path d="M27 24a2.96 2.96 0 0 0-1.285.3l-4.3-4.3H18v2h2.586l3.715 3.715A2.966 2.966 0 0 0 24 27a3 3 0 1 0 3-3Zm0 4a1 1 0 1 1 1-1 1 1 0 0 1-1 1ZM27 13a2.995 2.995 0 0 0-2.816 2H18v2h6.184A2.995 2.995 0 1 0 27 13Zm0 4a1 1 0 1 1 1-1 1 1 0 0 1-1 1ZM27 2a3.003 3.003 0 0 0-3 3 2.966 2.966 0 0 0 .348 1.373L20.596 10H18v2h3.404l4.4-4.252A2.999 2.999 0 1 0 27 2Zm0 4a1 1 0 1 1 1-1 1 1 0 0 1-1 1Z" />
- <path d="M18 6h2V4h-2a3.976 3.976 0 0 0-3 1.382A3.976 3.976 0 0 0 12 4h-1a9.01 9.01 0 0 0-9 9v6a9.01 9.01 0 0 0 9 9h1a3.976 3.976 0 0 0 3-1.382A3.976 3.976 0 0 0 18 28h2v-2h-2a2.002 2.002 0 0 1-2-2V8a2.002 2.002 0 0 1 2-2Zm-6 20h-1a7.005 7.005 0 0 1-6.92-6H6v-2H4v-4h3a3.003 3.003 0 0 0 3-3V9H8v2a1 1 0 0 1-1 1H4.08A7.005 7.005 0 0 1 11 6h1a2.002 2.002 0 0 1 2 2v4h-2v2h2v4h-2a3.003 3.003 0 0 0-3 3v2h2v-2a1 1 0 0 1 1-1h2v4a2.002 2.002 0 0 1-2 2Z" />
+ <path
+ d="M27 24a2.96 2.96 0 0 0-1.285.3l-4.3-4.3H18v2h2.586l3.715 3.715A2.966 2.966 0 0 0 24 27a3 3 0 1 0 3-3Zm0 4a1 1 0 1 1 1-1 1 1 0 0 1-1 1ZM27 13a2.995 2.995 0 0 0-2.816 2H18v2h6.184A2.995 2.995 0 1 0 27 13Zm0 4a1 1 0 1 1 1-1 1 1 0 0 1-1 1ZM27 2a3.003 3.003 0 0 0-3 3 2.966 2.966 0 0 0 .348 1.373L20.596 10H18v2h3.404l4.4-4.252A2.999 2.999 0 1 0 27 2Zm0 4a1 1 0 1 1 1-1 1 1 0 0 1-1 1Z"/>
+ <path
+ d="M18 6h2V4h-2a3.976 3.976 0 0 0-3 1.382A3.976 3.976 0 0 0 12 4h-1a9.01 9.01 0 0 0-9 9v6a9.01 9.01 0 0 0 9 9h1a3.976 3.976 0 0 0 3-1.382A3.976 3.976 0 0 0 18 28h2v-2h-2a2.002 2.002 0 0 1-2-2V8a2.002 2.002 0 0 1 2-2Zm-6 20h-1a7.005 7.005 0 0 1-6.92-6H6v-2H4v-4h3a3.003 3.003 0 0 0 3-3V9H8v2a1 1 0 0 1-1 1H4.08A7.005 7.005 0 0 1 11 6h1a2.002 2.002 0 0 1 2 2v4h-2v2h2v4h-2a3.003 3.003 0 0 0-3 3v2h2v-2a1 1 0 0 1 1-1h2v4a2.002 2.002 0 0 1-2 2Z"/>
<path
d="M0 0h32v32H0z"
data-name="<Transparent Rectangle>"
@@ -1383,6 +1425,7 @@ export function MachineLearningIcon() {
</svg>
);
}
+
export function TerminalIcon() {
return (
<svg
@@ -1392,8 +1435,9 @@ export function TerminalIcon() {
viewBox="0 0 32 32"
className="icon">
<title>{"terminal"}</title>
- <path d="M26 4.01H6a2 2 0 0 0-2 2v20a2 2 0 0 0 2 2h20a2 2 0 0 0 2-2v-20a2 2 0 0 0-2-2Zm0 2v4H6v-4Zm-20 20v-14h20v14Z" />
- <path d="m10.76 16.18 2.82 2.83-2.82 2.83 1.41 1.41 4.24-4.24-4.24-4.24-1.41 1.41z" />
+ <path
+ d="M26 4.01H6a2 2 0 0 0-2 2v20a2 2 0 0 0 2 2h20a2 2 0 0 0 2-2v-20a2 2 0 0 0-2-2Zm0 2v4H6v-4Zm-20 20v-14h20v14Z"/>
+ <path d="m10.76 16.18 2.82 2.83-2.82 2.83 1.41 1.41 4.24-4.24-4.24-4.24-1.41 1.41z"/>
<path
d="M0 0h32v32H0z"
data-name="<Transparent Rectangle>"
@@ -1404,6 +1448,7 @@ export function TerminalIcon() {
</svg>
);
}
+
export function SapIcon() {
return (
<svg
@@ -1429,6 +1474,7 @@ export function SapIcon() {
</svg>
);
}
+
export function ScriptIcon() {
return (
<svg
@@ -1438,8 +1484,10 @@ export function ScriptIcon() {
viewBox="0 0 32 32"
className="icon">
<title>{"script"}</title>
- <path d="m18.83 26 2.58-2.58L20 22l-4 4 4 4 1.42-1.41L18.83 26zM27.17 26l-2.58 2.58L26 30l4-4-4-4-1.42 1.41L27.17 26z" />
- <path d="M14 28H8V4h8v6a2.006 2.006 0 0 0 2 2h6v6h2v-8a.91.91 0 0 0-.3-.7l-7-7A.909.909 0 0 0 18 2H8a2.006 2.006 0 0 0-2 2v24a2.006 2.006 0 0 0 2 2h6Zm4-23.6 5.6 5.6H18Z" />
+ <path
+ d="m18.83 26 2.58-2.58L20 22l-4 4 4 4 1.42-1.41L18.83 26zM27.17 26l-2.58 2.58L26 30l4-4-4-4-1.42 1.41L27.17 26z"/>
+ <path
+ d="M14 28H8V4h8v6a2.006 2.006 0 0 0 2 2h6v6h2v-8a.91.91 0 0 0-.3-.7l-7-7A.909.909 0 0 0 18 2H8a2.006 2.006 0 0 0-2 2v24a2.006 2.006 0 0 0 2 2h6Zm4-23.6 5.6 5.6H18Z"/>
<path
d="M0 0h32v32H0z"
data-name="<Transparent Rectangle>"
@@ -1450,6 +1498,7 @@ export function ScriptIcon() {
</svg>
);
}
+
export function ValidationIcon() {
return (
<svg
@@ -1458,8 +1507,9 @@ export function ValidationIcon() {
height={800}
viewBox="0 0 32 32"
className="icon">
- <path d="m22 27.18-2.59-2.59L18 26l4 4 8-8-1.41-1.41L22 27.18z" />
- <path d="M15 28H8V4h8v6a2.006 2.006 0 0 0 2 2h6v6h2v-8a.91.91 0 0 0-.3-.7l-7-7A.909.909 0 0 0 18 2H8a2.006 2.006 0 0 0-2 2v24a2.006 2.006 0 0 0 2 2h7Zm3-23.6 5.6 5.6H18Z" />
+ <path d="m22 27.18-2.59-2.59L18 26l4 4 8-8-1.41-1.41L22 27.18z"/>
+ <path
+ d="M15 28H8V4h8v6a2.006 2.006 0 0 0 2 2h6v6h2v-8a.91.91 0 0 0-.3-.7l-7-7A.909.909 0 0 0 18 2H8a2.006 2.006 0 0 0-2 2v24a2.006 2.006 0 0 0 2 2h7Zm3-23.6 5.6 5.6H18Z"/>
<path
d="M0 0h32v32H0z"
data-name="<Transparent Rectangle>"
@@ -1470,6 +1520,7 @@ export function ValidationIcon() {
</svg>
);
}
+
export function OpenstackIcon() {
return (
<svg
@@ -1486,6 +1537,7 @@ export function OpenstackIcon() {
</svg>
);
}
+
export function OpenshiftIcon() {
return (
<svg
@@ -1530,6 +1582,7 @@ export function OpenshiftIcon() {
</svg>
);
}
+
export function RefIcon() {
return (
<svg
@@ -1538,8 +1591,9 @@ export function RefIcon() {
height={800}
viewBox="0 0 32 32"
className="icon">
- <path d="M4 20v2h4.586L2 28.586 3.414 30 10 23.414V28h2v-8H4zM30 30h-8v-8h8Zm-6-2h4v-4h-4ZM20 20h-8v-8h8Zm-6-2h4v-4h-4Z" />
- <path d="M24 17h-2v-2h2a4 4 0 0 0 0-8H12V5h12a6 6 0 0 1 0 12ZM10 10H2V2h8ZM4 8h4V4H4Z" />
+ <path
+ d="M4 20v2h4.586L2 28.586 3.414 30 10 23.414V28h2v-8H4zM30 30h-8v-8h8Zm-6-2h4v-4h-4ZM20 20h-8v-8h8Zm-6-2h4v-4h-4Z"/>
+ <path d="M24 17h-2v-2h2a4 4 0 0 0 0-8H12V5h12a6 6 0 0 1 0 12ZM10 10H2V2h8ZM4 8h4V4H4Z"/>
<path
d="M0 0h32v32H0z"
data-name="<Transparent Rectangle>"
@@ -1550,6 +1604,7 @@ export function RefIcon() {
</svg>
);
}
+
export function RedisIcon() {
return (
<svg
@@ -1598,6 +1653,7 @@ export function RedisIcon() {
</svg>
);
}
+
export function SearchIcon() {
return (
<svg
@@ -1606,7 +1662,8 @@ export function SearchIcon() {
height={800}
viewBox="0 0 32 32"
className="icon">
- <path d="m29 27.586-7.552-7.552a11.018 11.018 0 1 0-1.414 1.414L27.586 29ZM4 13a9 9 0 1 1 9 9 9.01 9.01 0 0 1-9-9Z" />
+ <path
+ d="m29 27.586-7.552-7.552a11.018 11.018 0 1 0-1.414 1.414L27.586 29ZM4 13a9 9 0 1 1 9 9 9.01 9.01 0 0 1-9-9Z"/>
<path
d="M0 0h32v32H0z"
data-name="<Transparent Rectangle>"
@@ -1617,6 +1674,7 @@ export function SearchIcon() {
</svg>
);
}
+
export function BlockchainIcon() {
return (
<svg
@@ -1626,7 +1684,7 @@ export function BlockchainIcon() {
viewBox="0 0 32 32"
className="icon">
<title>{"blockchain"}</title>
- <path d="M6 24H4V8h2ZM28 8h-2v16h2Zm-4-2V4H8v2Zm0 22v-2H8v2Z" />
+ <path d="M6 24H4V8h2ZM28 8h-2v16h2Zm-4-2V4H8v2Zm0 22v-2H8v2Z"/>
<path
d="M0 0h32v32H0z"
data-name="<Transparent Rectangle>"
@@ -1637,6 +1695,7 @@ export function BlockchainIcon() {
</svg>
);
}
+
export function ChatIcon() {
return (
<svg
@@ -1646,8 +1705,9 @@ export function ChatIcon() {
viewBox="0 0 32 32"
className="icon">
<title>{"chat"}</title>
- <path d="M17.74 30 16 29l4-7h6a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h9v2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4h20a4 4 0 0 1 4 4v12a4 4 0 0 1-4 4h-4.84Z" />
- <path d="M8 10h16v2H8zM8 16h10v2H8z" />
+ <path
+ d="M17.74 30 16 29l4-7h6a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h9v2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4h20a4 4 0 0 1 4 4v12a4 4 0 0 1-4 4h-4.84Z"/>
+ <path d="M8 10h16v2H8zM8 16h10v2H8z"/>
<path
d="M0 0h32v32H0z"
data-name="<Transparent Rectangle>"
@@ -1658,6 +1718,7 @@ export function ChatIcon() {
</svg>
);
}
+
export function WorkflowIcon() {
return (
<svg
@@ -1667,10 +1728,12 @@ export function WorkflowIcon() {
height={800}
viewBox="0 0 512 512"
className="icon">
- <path d="M445.66 49.341H340.206L294.008 3.143c-4.192-4.191-10.99-4.191-15.183 0l-49.341 49.342c-4.192 4.192-4.192 10.99 0 15.183l46.198 46.198v38.494h-80.516c-5.929 0-10.736 4.806-10.736 10.735v21.471h-42.942v-21.471c0-5.929-4.806-10.735-10.736-10.735H66.34c-5.929 0-10.735 4.806-10.735 10.735v64.413c0 5.929 4.806 10.735 10.735 10.735h20.755v93.798c0 5.929 4.806 10.736 10.735 10.736h95.437l-10.346 10.346c-4.192 4.192-4.193 10.99 0 15.182a10.7 10.7 0 0 0 7.591 3.144c2.747 0 5.4 [...]
+ <path
+ d="M445.66 49.341H340.206L294.008 3.143c-4.192-4.191-10.99-4.191-15.183 0l-49.341 49.342c-4.192 4.192-4.192 10.99 0 15.183l46.198 46.198v38.494h-80.516c-5.929 0-10.736 4.806-10.736 10.735v21.471h-42.942v-21.471c0-5.929-4.806-10.735-10.736-10.735H66.34c-5.929 0-10.735 4.806-10.735 10.735v64.413c0 5.929 4.806 10.735 10.735 10.735h20.755v93.798c0 5.929 4.806 10.736 10.735 10.736h95.437l-10.346 10.346c-4.192 4.192-4.193 10.99 0 15.182a10.7 10.7 0 0 0 7.591 3.144c2.747 0 5.495 [...]
</svg>
);
}
+
export function WebserviceIcon() {
return (
<svg
@@ -1680,7 +1743,8 @@ export function WebserviceIcon() {
viewBox="0 0 32 32"
className="icon">
<title>{"fog"}</title>
- <path d="M25.829 13.116A10.02 10.02 0 0 0 16 5v2a8.023 8.023 0 0 1 7.865 6.493l.259 1.346 1.349.245A5.502 5.502 0 0 1 24.508 26H16v2h8.508a7.502 7.502 0 0 0 1.32-14.884ZM8 24h6v2H8zM4 24h2v2H4zM6 20h8v2H6zM2 20h2v2H2zM8 16h6v2H8zM4 16h2v2H4zM10 12h4v2h-4zM6 12h2v2H6zM12 8h2v2h-2z" />
+ <path
+ d="M25.829 13.116A10.02 10.02 0 0 0 16 5v2a8.023 8.023 0 0 1 7.865 6.493l.259 1.346 1.349.245A5.502 5.502 0 0 1 24.508 26H16v2h8.508a7.502 7.502 0 0 0 1.32-14.884ZM8 24h6v2H8zM4 24h2v2H4zM6 20h8v2H6zM2 20h2v2H2zM8 16h6v2H8zM4 16h2v2H4zM10 12h4v2h-4zM6 12h2v2H6zM12 8h2v2h-2z"/>
<path
d="M0 0h32v32H0z"
data-name="<Transparent Rectangle>"
@@ -1691,6 +1755,7 @@ export function WebserviceIcon() {
</svg>
);
}
+
export function MobileIcon() {
return (
<svg
@@ -1699,9 +1764,10 @@ export function MobileIcon() {
height={800}
viewBox="0 0 32 32"
className="icon">
- <path d="M23 7h4v4h-4zM23 13h4v4h-4zM17 7h4v4h-4zM17 13h4v4h-4z" />
- <circle cx={14.5} cy={24.5} r={1.5} />
- <path d="M21 30H8a2.002 2.002 0 0 1-2-2V4a2.002 2.002 0 0 1 2-2h13v2H8v24h13v-8h2v8a2.002 2.002 0 0 1-2 2Z" />
+ <path d="M23 7h4v4h-4zM23 13h4v4h-4zM17 7h4v4h-4zM17 13h4v4h-4z"/>
+ <circle cx={14.5} cy={24.5} r={1.5}/>
+ <path
+ d="M21 30H8a2.002 2.002 0 0 1-2-2V4a2.002 2.002 0 0 1 2-2h13v2H8v24h13v-8h2v8a2.002 2.002 0 0 1-2 2Z"/>
<path
d="M0 0h32v32H0z"
data-name="<Transparent Rectangle>"
@@ -1712,6 +1778,7 @@ export function MobileIcon() {
</svg>
);
}
+
export function ClusterIcon() {
return (
<svg
@@ -1720,7 +1787,8 @@ export function ClusterIcon() {
height={800}
viewBox="0 0 32 32"
className="icon">
- <path d="M16 7a3 3 0 1 1 3-3 3.003 3.003 0 0 1-3 3Zm0-4a1 1 0 1 0 1 1 1.001 1.001 0 0 0-1-1ZM11 30a3 3 0 1 1 3-3 3.003 3.003 0 0 1-3 3Zm0-4a1 1 0 1 0 1 1 1.001 1.001 0 0 0-1-1ZM7 11a3 3 0 1 1 3-3 3.003 3.003 0 0 1-3 3Zm0-4a1 1 0 1 0 1 1 1.001 1.001 0 0 0-1-1ZM21 30a3 3 0 1 1 3-3 3.003 3.003 0 0 1-3 3Zm0-4a1 1 0 1 0 1 1 1.001 1.001 0 0 0-1-1ZM25 11a3 3 0 1 1 3-3 3.003 3.003 0 0 1-3 3Zm0-4a1 1 0 1 0 1 1 1.001 1.001 0 0 0-1-1ZM4 21a3 3 0 1 1 3-3 3.003 3.003 0 0 1-3 3Zm0-4a1 1 0 [...]
+ <path
+ d="M16 7a3 3 0 1 1 3-3 3.003 3.003 0 0 1-3 3Zm0-4a1 1 0 1 0 1 1 1.001 1.001 0 0 0-1-1ZM11 30a3 3 0 1 1 3-3 3.003 3.003 0 0 1-3 3Zm0-4a1 1 0 1 0 1 1 1.001 1.001 0 0 0-1-1ZM7 11a3 3 0 1 1 3-3 3.003 3.003 0 0 1-3 3Zm0-4a1 1 0 1 0 1 1 1.001 1.001 0 0 0-1-1ZM21 30a3 3 0 1 1 3-3 3.003 3.003 0 0 1-3 3Zm0-4a1 1 0 1 0 1 1 1.001 1.001 0 0 0-1-1ZM25 11a3 3 0 1 1 3-3 3.003 3.003 0 0 1-3 3Zm0-4a1 1 0 1 0 1 1 1.001 1.001 0 0 0-1-1ZM4 21a3 3 0 1 1 3-3 3.003 3.003 0 0 1-3 3Zm0-4a1 1 0 1 [...]
<path
d="M0 0h32v32H0z"
data-name="<Transparent Rectangle>"
@@ -1731,6 +1799,7 @@ export function ClusterIcon() {
</svg>
);
}
+
export function RpcIcon() {
return (
<svg
@@ -1739,7 +1808,7 @@ export function RpcIcon() {
height={800}
viewBox="0 0 32 32"
className="icon">
- <path d="m14 26 1.41-1.41L7.83 17H28v-2H7.83l7.58-7.59L14 6 4 16l10 10z" />
+ <path d="m14 26 1.41-1.41L7.83 17H28v-2H7.83l7.58-7.59L14 6 4 16l10 10z"/>
<path
d="M0 0h32v32H0z"
data-name="<Transparent Rectangle>"
@@ -1750,6 +1819,7 @@ export function RpcIcon() {
</svg>
);
}
+
export function InfinispanIcon() {
return (
<svg
@@ -1777,6 +1847,7 @@ export function InfinispanIcon() {
</svg>
);
}
+
export function TransformationIcon() {
return (
<svg
@@ -1786,7 +1857,8 @@ export function TransformationIcon() {
viewBox="0 0 32 32"
className="icon">
<title>{"data-share"}</title>
- <path d="M5 25v-9.172l-3.586 3.586L0 18l6-6 6 6-1.414 1.414L7 15.828V25h12v2H7a2.002 2.002 0 0 1-2-2ZM24 22h4a2.002 2.002 0 0 1 2 2v4a2.002 2.002 0 0 1-2 2h-4a2.002 2.002 0 0 1-2-2v-4a2.002 2.002 0 0 1 2-2Zm4 6v-4h-4.002L24 28ZM27 6v9.172l3.586-3.586L32 13l-6 6-6-6 1.414-1.414L25 15.172V6H13V4h12a2.002 2.002 0 0 1 2 2ZM2 6h6v2H2zM2 2h8v2H2z" />
+ <path
+ d="M5 25v-9.172l-3.586 3.586L0 18l6-6 6 6-1.414 1.414L7 15.828V25h12v2H7a2.002 2.002 0 0 1-2-2ZM24 22h4a2.002 2.002 0 0 1 2 2v4a2.002 2.002 0 0 1-2 2h-4a2.002 2.002 0 0 1-2-2v-4a2.002 2.002 0 0 1 2-2Zm4 6v-4h-4.002L24 28ZM27 6v9.172l3.586-3.586L32 13l-6 6-6-6 1.414-1.414L25 15.172V6H13V4h12a2.002 2.002 0 0 1 2 2ZM2 6h6v2H2zM2 2h8v2H2z"/>
<path
d="M0 0h32v32H0z"
data-name="<Transparent Rectangle>"
@@ -1797,6 +1869,7 @@ export function TransformationIcon() {
</svg>
);
}
+
export function TestingIcon() {
return (
<svg
@@ -1805,8 +1878,10 @@ export function TestingIcon() {
height={800}
viewBox="0 0 32 32"
className="icon">
- <path d="M28 17v5H4V6h10V4H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h8v4H8v2h16v-2h-4v-4h8a2 2 0 0 0 2-2v-5ZM18 28h-4v-4h4Z" />
- <path d="M30 10V8h-2.101a4.968 4.968 0 0 0-.732-1.753l1.49-1.49-1.414-1.414-1.49 1.49A4.968 4.968 0 0 0 24 4.101V2h-2v2.101a4.968 4.968 0 0 0-1.753.732l-1.49-1.49-1.414 1.414 1.49 1.49A4.968 4.968 0 0 0 18.101 8H16v2h2.101a4.968 4.968 0 0 0 .732 1.753l-1.49 1.49 1.414 1.414 1.49-1.49a4.968 4.968 0 0 0 1.753.732V16h2v-2.101a4.968 4.968 0 0 0 1.753-.732l1.49 1.49 1.414-1.414-1.49-1.49A4.968 4.968 0 0 0 27.899 10Zm-7 2a3 3 0 1 1 3-3 3.003 3.003 0 0 1-3 3Z" />
+ <path
+ d="M28 17v5H4V6h10V4H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h8v4H8v2h16v-2h-4v-4h8a2 2 0 0 0 2-2v-5ZM18 28h-4v-4h4Z"/>
+ <path
+ d="M30 10V8h-2.101a4.968 4.968 0 0 0-.732-1.753l1.49-1.49-1.414-1.414-1.49 1.49A4.968 4.968 0 0 0 24 4.101V2h-2v2.101a4.968 4.968 0 0 0-1.753.732l-1.49-1.49-1.414 1.414 1.49 1.49A4.968 4.968 0 0 0 18.101 8H16v2h2.101a4.968 4.968 0 0 0 .732 1.753l-1.49 1.49 1.414 1.414 1.49-1.49a4.968 4.968 0 0 0 1.753.732V16h2v-2.101a4.968 4.968 0 0 0 1.753-.732l1.49 1.49 1.414-1.414-1.49-1.49A4.968 4.968 0 0 0 27.899 10Zm-7 2a3 3 0 1 1 3-3 3.003 3.003 0 0 1-3 3Z"/>
<path
d="M0 0h32v32H0z"
data-name="<Transparent Rectangle>"
@@ -1817,6 +1892,7 @@ export function TestingIcon() {
</svg>
);
}
+
export function ApiIcon() {
return (
<svg
@@ -1826,7 +1902,8 @@ export function ApiIcon() {
viewBox="0 0 32 32"
className="icon">
<title>{"api"}</title>
- <path d="M26 22a3.86 3.86 0 0 0-2 .57l-3.09-3.1a6 6 0 0 0 0-6.94L24 9.43a3.86 3.86 0 0 0 2 .57 4 4 0 1 0-4-4 3.86 3.86 0 0 0 .57 2l-3.1 3.09a6 6 0 0 0-6.94 0L9.43 8A3.86 3.86 0 0 0 10 6a4 4 0 1 0-4 4 3.86 3.86 0 0 0 2-.57l3.09 3.1a6 6 0 0 0 0 6.94L8 22.57A3.86 3.86 0 0 0 6 22a4 4 0 1 0 4 4 3.86 3.86 0 0 0-.57-2l3.1-3.09a6 6 0 0 0 6.94 0l3.1 3.09a3.86 3.86 0 0 0-.57 2 4 4 0 1 0 4-4Zm0-18a2 2 0 1 1-2 2 2 2 0 0 1 2-2ZM4 6a2 2 0 1 1 2 2 2 2 0 0 1-2-2Zm2 22a2 2 0 1 1 2-2 2 2 0 0 1 [...]
+ <path
+ d="M26 22a3.86 3.86 0 0 0-2 .57l-3.09-3.1a6 6 0 0 0 0-6.94L24 9.43a3.86 3.86 0 0 0 2 .57 4 4 0 1 0-4-4 3.86 3.86 0 0 0 .57 2l-3.1 3.09a6 6 0 0 0-6.94 0L9.43 8A3.86 3.86 0 0 0 10 6a4 4 0 1 0-4 4 3.86 3.86 0 0 0 2-.57l3.09 3.1a6 6 0 0 0 0 6.94L8 22.57A3.86 3.86 0 0 0 6 22a4 4 0 1 0 4 4 3.86 3.86 0 0 0-.57-2l3.1-3.09a6 6 0 0 0 6.94 0l3.1 3.09a3.86 3.86 0 0 0-.57 2 4 4 0 1 0 4-4Zm0-18a2 2 0 1 1-2 2 2 2 0 0 1 2-2ZM4 6a2 2 0 1 1 2 2 2 2 0 0 1-2-2Zm2 22a2 2 0 1 1 2-2 2 2 0 0 1-2 [...]
<path
d="M0 0h32v32H0z"
style={{
@@ -1836,6 +1913,7 @@ export function ApiIcon() {
</svg>
);
}
+
export function MonitoringIcon() {
return (
<svg
@@ -1844,8 +1922,10 @@ export function MonitoringIcon() {
height={800}
viewBox="0 0 32 32"
className="icon">
- <path d="M28 16v6H4V6h7V4H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h8v4H8v2h16v-2h-4v-4h8a2 2 0 0 0 2-2v-6ZM18 28h-4v-4h4Z" />
- <path d="M18 18h-.01a1 1 0 0 1-.951-.725L15.246 11H11V9h5a1 1 0 0 1 .962.725l1.074 3.76 3.009-9.78A1.014 1.014 0 0 1 22 3a.98.98 0 0 1 .949.684L24.72 9H30v2h-6a1 1 0 0 1-.949-.684l-1.013-3.04-3.082 10.018A1 1 0 0 1 18 18Z" />
+ <path
+ d="M28 16v6H4V6h7V4H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h8v4H8v2h16v-2h-4v-4h8a2 2 0 0 0 2-2v-6ZM18 28h-4v-4h4Z"/>
+ <path
+ d="M18 18h-.01a1 1 0 0 1-.951-.725L15.246 11H11V9h5a1 1 0 0 1 .962.725l1.074 3.76 3.009-9.78A1.014 1.014 0 0 1 22 3a.98.98 0 0 1 .949.684L24.72 9H30v2h-6a1 1 0 0 1-.949-.684l-1.013-3.04-3.082 10.018A1 1 0 0 1 18 18Z"/>
<path
d="M0 0h32v32H0z"
data-name="<Transparent Rectangle>"
@@ -1856,6 +1936,7 @@ export function MonitoringIcon() {
</svg>
);
}
+
export function NetworkingIcon() {
return (
<svg
@@ -1865,13 +1946,18 @@ export function NetworkingIcon() {
height={800}
viewBox="0 0 511.984 511.984"
className="icon">
- <path d="M207.29 287.991H48.735C30.687 287.991 16 302.375 16 320.054v111.868c0 17.679 14.688 32.063 32.735 32.063H207.29c18.031 0 32.703-14.384 32.703-32.063V320.054c0-17.679-14.672-32.063-32.703-32.063zm0 143.996-159.291-.064.736-111.932 159.259.064.048 111.645c0 .015-.192.287-.752.287z" />
- <path d="M383.988 239.992H127.996c-8.832 0-16 7.168-16 16v47.998c0 8.832 7.168 16 16 16s15.999-7.168 15.999-16v-31.999h223.993v31.999c0 8.832 7.168 16 16 16s15.999-7.168 15.999-16v-47.998c0-8.832-7.167-16-15.999-16z" />
- <path d="M463.25 287.991H304.727c-18.047 0-32.735 14.384-32.735 32.063v111.868c0 17.679 14.688 32.063 32.735 32.063H463.25c18.047 0 32.735-14.384 32.735-32.063V320.054c-.001-17.679-14.688-32.063-32.735-32.063zm0 143.996-159.259-.064.736-111.932 159.259.064.064 111.645c-.016-.001-.208.287-.8.287zM415.987 479.985h-63.998c-8.832 0-16 7.168-16 16s7.168 16 16 16h63.998c8.832 0 16-7.168 16-16s-7.168-16-16-16zM335.253 0H176.73c-18.047 0-32.735 14.384-32.735 32.063v111.869c0 17.679 1 [...]
- <path d="M255.992 191.994c-8.832 0-16 7.168-16 16v47.998c0 8.832 7.168 16 16 16s15.999-7.168 15.999-16v-47.998c.001-8.832-7.167-16-15.999-16z" />
+ <path
+ d="M207.29 287.991H48.735C30.687 287.991 16 302.375 16 320.054v111.868c0 17.679 14.688 32.063 32.735 32.063H207.29c18.031 0 32.703-14.384 32.703-32.063V320.054c0-17.679-14.672-32.063-32.703-32.063zm0 143.996-159.291-.064.736-111.932 159.259.064.048 111.645c0 .015-.192.287-.752.287z"/>
+ <path
+ d="M383.988 239.992H127.996c-8.832 0-16 7.168-16 16v47.998c0 8.832 7.168 16 16 16s15.999-7.168 15.999-16v-31.999h223.993v31.999c0 8.832 7.168 16 16 16s15.999-7.168 15.999-16v-47.998c0-8.832-7.167-16-15.999-16z"/>
+ <path
+ d="M463.25 287.991H304.727c-18.047 0-32.735 14.384-32.735 32.063v111.868c0 17.679 14.688 32.063 32.735 32.063H463.25c18.047 0 32.735-14.384 32.735-32.063V320.054c-.001-17.679-14.688-32.063-32.735-32.063zm0 143.996-159.259-.064.736-111.932 159.259.064.064 111.645c-.016-.001-.208.287-.8.287zM415.987 479.985h-63.998c-8.832 0-16 7.168-16 16s7.168 16 16 16h63.998c8.832 0 16-7.168 16-16s-7.168-16-16-16zM335.253 0H176.73c-18.047 0-32.735 14.384-32.735 32.063v111.869c0 17.679 14. [...]
+ <path
+ d="M255.992 191.994c-8.832 0-16 7.168-16 16v47.998c0 8.832 7.168 16 16 16s15.999-7.168 15.999-16v-47.998c.001-8.832-7.167-16-15.999-16z"/>
</svg>
);
}
+
export function HealthIcon() {
return (
<svg
@@ -1889,10 +1975,11 @@ export function HealthIcon() {
d="M27 7h0c-2.6-2.7-6.9-2.7-9.5 0l-1.3 1.4c-.1.1-.4.1-.5 0L14.4 7C11.8 4.3 7.6 4.3 5 7h0c-2.6 2.7-2.6 7.1 0 9.8l1.6 1.6 9.2 9.5c.1.1.4.1.5 0l9.2-9.5 1.6-1.6c2.6-2.7 2.6-7.1-.1-9.8z"
className="st0"
/>
- <path d="M9 15h3l2-2 2 4 2-5 2 3h3" className="st0" />
+ <path d="M9 15h3l2-2 2 4 2-5 2 3h3" className="st0"/>
</svg>
);
}
+
export function KubernetesIcon() {
return (
<svg
@@ -1925,6 +2012,7 @@ export function KubernetesIcon() {
</svg>
);
}
+
export function DocumentIcon() {
return (
<svg
@@ -1934,7 +2022,8 @@ export function DocumentIcon() {
viewBox="0 0 32 32"
className="icon">
<title>{"document--blank"}</title>
- <path d="m25.7 9.3-7-7A.908.908 0 0 0 18 2H8a2.006 2.006 0 0 0-2 2v24a2.006 2.006 0 0 0 2 2h16a2.006 2.006 0 0 0 2-2V10a.908.908 0 0 0-.3-.7ZM18 4.4l5.6 5.6H18ZM24 28H8V4h8v6a2.006 2.006 0 0 0 2 2h6Z" />
+ <path
+ d="m25.7 9.3-7-7A.908.908 0 0 0 18 2H8a2.006 2.006 0 0 0-2 2v24a2.006 2.006 0 0 0 2 2h16a2.006 2.006 0 0 0 2-2V10a.908.908 0 0 0-.3-.7ZM18 4.4l5.6 5.6H18ZM24 28H8V4h8v6a2.006 2.006 0 0 0 2 2h6Z"/>
<path
d="M0 0h32v32H0z"
data-name="<Transparent Rectangle>"
@@ -1945,6 +2034,7 @@ export function DocumentIcon() {
</svg>
);
}
+
export function GitIcon() {
return (
<svg
@@ -1963,6 +2053,7 @@ export function GitIcon() {
</svg>
);
}
+
export function SocialIcon() {
return (
<svg
@@ -1990,6 +2081,7 @@ export function SocialIcon() {
</svg>
);
}
+
export function DebeziumIcon() {
return (
<svg
@@ -2006,8 +2098,8 @@ export function DebeziumIcon() {
y2={114.02}
gradientUnits="userSpaceOnUse"
>
- <stop offset={0} stopColor="#91d443" />
- <stop offset={1} stopColor="#48bfe0" />
+ <stop offset={0} stopColor="#91d443"/>
+ <stop offset={1} stopColor="#48bfe0"/>
</linearGradient>
<linearGradient
xlinkHref="#linear-gradient"
@@ -2078,6 +2170,7 @@ export function DebeziumIcon() {
</svg>
);
}
+
export function IgniteIcon() {
return (
<svg
@@ -2098,6 +2191,7 @@ export function IgniteIcon() {
</svg>
);
}
+
export function HazelcastIcon() {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 133.3 133.3" className="icon">
@@ -2135,6 +2229,7 @@ export function HazelcastIcon() {
</svg>
);
}
+
export function AzureIcon() {
return (
<svg
@@ -2153,8 +2248,8 @@ export function AzureIcon() {
gradientTransform="matrix(1 0 0 -1 1075 158)"
gradientUnits="userSpaceOnUse"
>
- <stop offset={0} stopColor="#114a8b" />
- <stop offset={1} stopColor="#0669bc" />
+ <stop offset={0} stopColor="#114a8b"/>
+ <stop offset={1} stopColor="#0669bc"/>
</linearGradient>
<linearGradient
id="b"
@@ -2165,11 +2260,11 @@ export function AzureIcon() {
gradientTransform="matrix(1 0 0 -1 1075 158)"
gradientUnits="userSpaceOnUse"
>
- <stop offset={0} stopOpacity={0.3} />
- <stop offset={0.071} stopOpacity={0.2} />
- <stop offset={0.321} stopOpacity={0.1} />
- <stop offset={0.623} stopOpacity={0.05} />
- <stop offset={1} stopOpacity={0} />
+ <stop offset={0} stopOpacity={0.3}/>
+ <stop offset={0.071} stopOpacity={0.2}/>
+ <stop offset={0.321} stopOpacity={0.1}/>
+ <stop offset={0.623} stopOpacity={0.05}/>
+ <stop offset={1} stopOpacity={0}/>
</linearGradient>
<linearGradient
id="c"
@@ -2180,8 +2275,8 @@ export function AzureIcon() {
gradientTransform="matrix(1 0 0 -1 1075 158)"
gradientUnits="userSpaceOnUse"
>
- <stop offset={0} stopColor="#3ccbf4" />
- <stop offset={1} stopColor="#2892df" />
+ <stop offset={0} stopColor="#3ccbf4"/>
+ <stop offset={1} stopColor="#2892df"/>
</linearGradient>
</defs>
<path
diff --git a/karavan-designer/src/index.css b/karavan-designer/src/index.css
index d2ffcae4..a7341d7a 100644
--- a/karavan-designer/src/index.css
+++ b/karavan-designer/src/index.css
@@ -8,14 +8,8 @@ body,
#root {
margin: 0;
height: 100%;
-}
-
-#root {
display: flex;
flex-direction: column;
-}
-
-.karavan .pf-v5-c-page__main {
overflow: hidden;
}
diff --git a/karavan-designer/src/index.tsx b/karavan-designer/src/index.tsx
index 53d84ae5..5a495994 100644
--- a/karavan-designer/src/index.tsx
+++ b/karavan-designer/src/index.tsx
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import React from 'react';
+import React, {StrictMode} from 'react';
import "./index.css";
import "@patternfly/patternfly/patternfly.css";
import App from "./App";
@@ -22,4 +22,8 @@ import {createRoot} from "react-dom/client";
const container = document.getElementById('root');
const root = createRoot(container!);
-root.render(<App/>);
\ No newline at end of file
+root.render(
+ // <StrictMode>
+ <App />
+ // </StrictMode>
+);
\ No newline at end of file