This is an automated email from the ASF dual-hosted git repository.

marat pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-karavan.git

commit 39c7e89e9694538b841a866c35d421da0c59712c
Author: Marat Gubaidullin <[email protected]>
AuthorDate: Mon Nov 28 11:09:03 2022 -0500

    Code templates in application
---
 .../camel/karavan/api/ProjectFileResource.java     |   6 +-
 .../apache/camel/karavan/model/ProjectFile.java    |  10 ++
 .../apache/camel/karavan/service/CodeService.java  |  17 ++-
 ...arkus-org.apache.camel.AggregationStrategy.java |  23 +++
 .../quarkus-org.apache.camel.Processor.java        |  14 ++
 ...-boot-org.apache.camel.AggregationStrategy.java |  20 +++
 .../spring-boot-org.apache.camel.Processor.java    |  12 ++
 .../main/webui/src/designer/KaravanDesigner.tsx    |  56 ++++---
 .../webui/src/designer/error/ErrorHandlerCard.tsx  |  48 ++++++
 .../src/designer/error/ErrorHandlerDesigner.tsx    | 164 +++++++++++++++++++++
 .../src/main/webui/src/designer/karavan.css        |  12 +-
 .../webui/src/designer/route/RouteDesigner.tsx     |   1 +
 .../designer/route/property/DslPropertyField.tsx   | 104 ++++++++-----
 .../src/designer/route/property/ModalEditor.tsx    |  98 ++++++++++++
 .../src/main/webui/src/designer/utils/CamelUi.tsx  |  42 +++++-
 .../src/main/webui/src/projects/ProjectPage.tsx    |  24 ++-
 .../main/webui/src/projects/ProjectPageToolbar.tsx |   2 +-
 .../designer/route/property/DslPropertyField.tsx   |  22 +--
 18 files changed, 589 insertions(+), 86 deletions(-)

diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectFileResource.java
 
b/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectFileResource.java
index 5069200..747e34d 100644
--- 
a/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectFileResource.java
+++ 
b/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectFileResource.java
@@ -33,7 +33,9 @@ import javax.ws.rs.core.MediaType;
 import java.net.URLDecoder;
 import java.nio.charset.StandardCharsets;
 import java.time.Instant;
+import java.util.Comparator;
 import java.util.List;
+import java.util.stream.Collectors;
 
 @Path("/api/file")
 public class ProjectFileResource {
@@ -49,7 +51,9 @@ public class ProjectFileResource {
     @Path("/{projectId}")
     public List<ProjectFile> get(@HeaderParam("username") String username,
                                  @PathParam("projectId") String projectId) 
throws Exception {
-        return infinispanService.getProjectFiles(projectId);
+        return infinispanService.getProjectFiles(projectId).stream()
+                .sorted(Comparator.comparing(ProjectFile::getName))
+                .collect(Collectors.toList());
     }
 
     @POST
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/model/ProjectFile.java 
b/karavan-app/src/main/java/org/apache/camel/karavan/model/ProjectFile.java
index 6732b91..ad1d6c6 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/model/ProjectFile.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/model/ProjectFile.java
@@ -60,4 +60,14 @@ public class ProjectFile {
     public void setLastUpdate(Long lastUpdate) {
         this.lastUpdate = lastUpdate;
     }
+
+    @Override
+    public String toString() {
+        return "ProjectFile{" +
+                "name='" + name + '\'' +
+                ", code='" + code + '\'' +
+                ", projectId='" + projectId + '\'' +
+                ", lastUpdate=" + lastUpdate +
+                '}';
+    }
 }
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/service/CodeService.java 
b/karavan-app/src/main/java/org/apache/camel/karavan/service/CodeService.java
index ee7c57c..4fb5e67 100644
--- 
a/karavan-app/src/main/java/org/apache/camel/karavan/service/CodeService.java
+++ 
b/karavan-app/src/main/java/org/apache/camel/karavan/service/CodeService.java
@@ -38,6 +38,7 @@ import java.io.BufferedReader;
 import java.io.FileNotFoundException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -83,10 +84,18 @@ public class CodeService {
     }
 
     public Map<String,String> getApplicationPropertiesTemplates() {
-        Map<String, String> result = new HashMap<>(4);
-        List.of("quarkus", "spring-boot").forEach(runtime -> {
-            List.of("openshift", "kubernetes").forEach(target -> {
-                String templateName = runtime + "-" + target + 
"-application.properties";
+        Map<String, String> result = new HashMap<>();
+
+        List<String> runtimes = List.of("quarkus", "spring-boot");
+        List<String> targets = List.of("openshift", "kubernetes");
+        List<String> interfaces = 
List.of("org.apache.camel.AggregationStrategy.java", 
"org.apache.camel.Processor.java");
+
+        List<String> files = new ArrayList<>(interfaces);
+        files.addAll(targets.stream().map(target -> target + 
"-application.properties").collect(Collectors.toList()));
+
+        runtimes.forEach(runtime -> {
+            files.forEach(file -> {
+                String templateName = runtime + "-" + file;
                 String templatePath = "/snippets/" + templateName;
                 String templateText = getResourceFile(templatePath);
                 result.put(templateName, templateText);
diff --git 
a/karavan-app/src/main/resources/snippets/quarkus-org.apache.camel.AggregationStrategy.java
 
b/karavan-app/src/main/resources/snippets/quarkus-org.apache.camel.AggregationStrategy.java
new file mode 100644
index 0000000..4e45fcd
--- /dev/null
+++ 
b/karavan-app/src/main/resources/snippets/quarkus-org.apache.camel.AggregationStrategy.java
@@ -0,0 +1,23 @@
+import org.apache.camel.AggregationStrategy
+;
+import org.apache.camel.Exchange;
+
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+@Singleton
+@Named("NAME")
+public class NAME implements AggregationStrategy {
+    @Override
+    public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
+
+        if (oldExchange == null) {
+            return newExchange;
+        }
+
+        String oldBody = oldExchange.getIn().getBody(String.class);
+        String newBody = newExchange.getIn().getBody(String.class);
+        oldExchange.getIn().setBody(oldBody + "+" + newBody);
+        return oldExchange;
+    }
+}
\ No newline at end of file
diff --git 
a/karavan-app/src/main/resources/snippets/quarkus-org.apache.camel.Processor.java
 
b/karavan-app/src/main/resources/snippets/quarkus-org.apache.camel.Processor.java
new file mode 100644
index 0000000..6b81323
--- /dev/null
+++ 
b/karavan-app/src/main/resources/snippets/quarkus-org.apache.camel.Processor.java
@@ -0,0 +1,14 @@
+import org.apache.camel.Exchange;
+import org.apache.camel.Processor;
+
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+@Singleton
+@Named("NAME")
+public class NAME implements Processor {
+
+  public void process(Exchange exchange) throws Exception {
+      exchange.getIn().setBody("Hello World");
+  }
+}
\ No newline at end of file
diff --git 
a/karavan-app/src/main/resources/snippets/spring-boot-org.apache.camel.AggregationStrategy.java
 
b/karavan-app/src/main/resources/snippets/spring-boot-org.apache.camel.AggregationStrategy.java
new file mode 100644
index 0000000..1b04d76
--- /dev/null
+++ 
b/karavan-app/src/main/resources/snippets/spring-boot-org.apache.camel.AggregationStrategy.java
@@ -0,0 +1,20 @@
+import org.apache.camel.AggregationStrategy;
+import org.apache.camel.Exchange;
+
+import org.springframework.stereotype.Component;
+
+@Component("NAME")
+public class NAME implements AggregationStrategy {
+    @Override
+    public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
+
+        if (oldExchange == null) {
+            return newExchange;
+        }
+
+        String oldBody = oldExchange.getIn().getBody(String.class);
+        String newBody = newExchange.getIn().getBody(String.class);
+        oldExchange.getIn().setBody(oldBody + "+" + newBody);
+        return oldExchange;
+    }
+}
\ No newline at end of file
diff --git 
a/karavan-app/src/main/resources/snippets/spring-boot-org.apache.camel.Processor.java
 
b/karavan-app/src/main/resources/snippets/spring-boot-org.apache.camel.Processor.java
new file mode 100644
index 0000000..4593b74
--- /dev/null
+++ 
b/karavan-app/src/main/resources/snippets/spring-boot-org.apache.camel.Processor.java
@@ -0,0 +1,12 @@
+import org.apache.camel.Exchange;
+import org.apache.camel.Processor;
+
+import org.springframework.stereotype.Component;
+
+@Component("NAME")
+public class NAME implements Processor {
+
+  public void process(Exchange exchange) throws Exception {
+      exchange.getIn().setBody("Hello World");
+  }
+}
\ No newline at end of file
diff --git a/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx 
b/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx
index 6333b37..4b43ff4 100644
--- a/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx
+++ b/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx
@@ -27,13 +27,13 @@ import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
 import {CamelUi} from "./utils/CamelUi";
 import {BeansDesigner} from "./beans/BeansDesigner";
 import {RestDesigner} from "./rest/RestDesigner";
-import {ErrorDesigner} from "./error/ErrorDesigner";
-import {ExceptionDesigner} from "./exception/ExceptionDesigner";
+import {ErrorHandlerDesigner} from "./error/ErrorHandlerDesigner";
 import {getDesignerIcon} from "./utils/KaravanIcons";
 
 interface Props {
-    onSave?: (filename: string, yaml: string, propertyOnly: boolean) => void
-    onDisableHelp?: () => void
+    onSave: (filename: string, yaml: string, propertyOnly: boolean) => void
+    onSaveCustomCode: (name: string, code: string) => void
+    onGetCustomCode: (name: string) => Promise<string | undefined>
     filename: string
     yaml: string
     dark: boolean
@@ -48,6 +48,22 @@ interface State {
     routeDesignerRef?: any
 }
 
+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 => {
@@ -66,6 +82,10 @@ export class KaravanDesigner extends React.Component<Props, 
State> {
         routeDesignerRef: React.createRef(),
     }
 
+    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);
@@ -112,21 +132,21 @@ export class KaravanDesigner extends 
React.Component<Props, State> {
                     <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>
-                    <Tab eventKey='error' title={this.getTab("Error", "Error 
Handler", "error")}></Tab>
+                    <Tab eventKey='error' title={this.getTab("Error Handler", 
"Global Error Handler", "error")}></Tab>
                 </Tabs>
-                {tab === 'routes' && <RouteDesigner 
integration={this.state.integration}
-                                                    onSave={(integration, 
propertyOnly) => this.save(integration, propertyOnly)}
-                                                    dark={this.props.dark}
-                                                    
ref={this.state.routeDesignerRef}/>}
-                {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}/>}
-                {tab === 'error' && <ErrorDesigner 
integration={this.state.integration}
-                                                   onSave={(integration, 
propertyOnly) => this.save(integration, propertyOnly)}
-                                                   dark={this.props.dark}/>}
+                    {tab === 'routes' && <RouteDesigner 
integration={this.state.integration}
+                                                        onSave={(integration, 
propertyOnly) => this.save(integration, propertyOnly)}
+                                                        dark={this.props.dark}
+                                                        
ref={this.state.routeDesignerRef}/>}
+                    {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}/>}
+                    {tab === 'error' && <ErrorHandlerDesigner 
integration={this.state.integration}
+                                                              
onSave={(integration, propertyOnly) => this.save(integration, propertyOnly)}
+                                                              
dark={this.props.dark}/>}
             </PageSection>
         )
     }
diff --git a/karavan-app/src/main/webui/src/designer/error/ErrorHandlerCard.tsx 
b/karavan-app/src/main/webui/src/designer/error/ErrorHandlerCard.tsx
new file mode 100644
index 0000000..f0d534c
--- /dev/null
+++ b/karavan-app/src/main/webui/src/designer/error/ErrorHandlerCard.tsx
@@ -0,0 +1,48 @@
+/*
+ * 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
+} from '@patternfly/react-core';
+import '../karavan.css';
+import {ErrorHandlerDefinition} from "karavan-core/lib/model/CamelDefinition";
+import DeleteIcon from 
"@patternfly/react-icons/dist/js/icons/times-circle-icon";
+
+interface Props {
+    errorHandler: ErrorHandlerDefinition
+    deleteElement: (element: ErrorHandlerDefinition) => void
+}
+
+export class ErrorHandlerCard extends React.Component<Props, any> {
+
+    delete = (evt: React.MouseEvent) => {
+        evt.stopPropagation();
+        this.props.deleteElement.call(this, this.props.errorHandler);
+    }
+
+    render() {
+        return (
+            <div className="rest-card rest-card-selected">
+                <div className="header">
+                    <div className="title">Error Handler</div>
+                    <div className="description">Global error handler for the 
RouteBuilder</div>
+                    <Button variant="link" className="delete-button" 
onClick={e => this.delete(e)}><DeleteIcon/></Button>
+                </div>
+            </div>
+        );
+    }
+}
diff --git 
a/karavan-app/src/main/webui/src/designer/error/ErrorHandlerDesigner.tsx 
b/karavan-app/src/main/webui/src/designer/error/ErrorHandlerDesigner.tsx
new file mode 100644
index 0000000..ba35ccc
--- /dev/null
+++ b/karavan-app/src/main/webui/src/designer/error/ErrorHandlerDesigner.tsx
@@ -0,0 +1,164 @@
+/*
+ * 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, Drawer, DrawerContent, DrawerContentBody, DrawerPanelContent, 
Modal, PageSection
+} from '@patternfly/react-core';
+import '../karavan.css';
+import {ErrorHandlerDefinition} from "karavan-core/lib/model/CamelDefinition";
+import {CamelElement, 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 {CamelUtil} from "karavan-core/lib/api/CamelUtil";
+import {ErrorHandlerCard} from "./ErrorHandlerCard";
+import {DslProperties} from "../route/DslProperties";
+
+interface Props {
+    onSave?: (integration: Integration, propertyOnly: boolean) => void
+    integration: Integration
+    dark: boolean
+}
+
+interface State {
+    integration: Integration
+    showDeleteConfirmation: boolean
+    errorHandler?: ErrorHandlerDefinition
+    key: string
+    propertyOnly: boolean
+}
+
+export class ErrorHandlerDesigner extends React.Component<Props, State> {
+
+    public state: State = {
+        integration: this.props.integration,
+        showDeleteConfirmation: false,
+        key: "",
+        propertyOnly: false
+    }
+
+    componentDidMount() {
+        this.setState({key: Math.random().toString()})
+    }
+
+    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 = (errorHandler: ErrorHandlerDefinition) => {
+        this.setState({errorHandler: errorHandler, showDeleteConfirmation: 
true});
+    }
+
+    onIntegrationUpdate = (i: Integration) => {
+        this.setState({integration: i, propertyOnly: false, 
showDeleteConfirmation: false, key: Math.random().toString()});
+    }
+
+    deleteErrorHandler = () => {
+        const i = 
CamelDefinitionApiExt.deleteErrorHandlerFromIntegration(this.state.integration);
+        this.setState({
+            integration: i,
+            showDeleteConfirmation: false,
+            key: Math.random().toString(),
+            errorHandler: undefined,
+            propertyOnly: false
+        });
+    }
+
+    changeErrorHandler = (errorHandler: ErrorHandlerDefinition) => {
+        const clone = CamelUtil.cloneIntegration(this.state.integration);
+        const i = CamelDefinitionApiExt.addErrorHandlerToIntegration(clone, 
errorHandler);
+        this.setState({integration: i, propertyOnly: false, key: 
Math.random().toString(), errorHandler: errorHandler});
+    }
+
+    getDeleteConfirmation() {
+        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.deleteErrorHandler()}>Delete</Button>,
+                <Button key="cancel" variant="link"
+                        onClick={e => this.setState({showDeleteConfirmation: 
false})}>Cancel</Button>
+            ]}
+            onEscapePress={e => this.setState({showDeleteConfirmation: 
false})}>
+            <div>
+                Delete Global Error Handler from integration?
+            </div>
+        </Modal>)
+    }
+
+    createErrorHandlerErrorHandle = () => {
+        this.changeErrorHandler(new ErrorHandlerDefinition());
+    }
+
+    onPropertyUpdate = (element: CamelElement) => {
+        const clone = CamelUtil.cloneIntegration(this.state.integration);
+        const i = CamelDefinitionApiExt.addErrorHandlerToIntegration(clone, 
element);
+        this.setState({integration: i, propertyOnly: true, key: 
Math.random().toString()});
+    }
+
+    getPropertiesPanel(errorHandler?: ErrorHandlerDefinition) {
+        return (
+            <DrawerPanelContent isResizable hasNoBorder defaultSize={'400px'} 
maxSize={'800px'} minSize={'300px'}>
+                <DslProperties
+                    integration={this.props.integration}
+                    step={errorHandler}
+                    onIntegrationUpdate={this.onIntegrationUpdate}
+                    onPropertyUpdate={this.onPropertyUpdate}
+                    clipboardStep={undefined}
+                    isRouteDesigner={false}
+                    onClone={element => {}}
+                    dark={this.props.dark}/>
+            </DrawerPanelContent>
+        )
+    }
+
+    render() {
+        const errorHandler = CamelUi.getErrorHandler(this.state.integration);
+        return (
+            <PageSection className="rest-page" isFilled padding={{default: 
'noPadding'}}>
+                <div className="rest-page-columns">
+                    <Drawer isExpanded isInline>
+                        <DrawerContent 
panelContent={this.getPropertiesPanel(errorHandler)}>
+                            <DrawerContentBody>
+                                <div className="graph" data-click="REST">
+                                    <div className="flows">
+                                        {errorHandler && <ErrorHandlerCard 
key={errorHandler.uuid + this.state.key}
+                                                                           
errorHandler={errorHandler}
+                                                                           
deleteElement={this.showDeleteConfirmation}/>}
+                                        <div className="add-rest">
+                                            {errorHandler === undefined && 
<Button
+                                                variant="primary"
+                                                data-click="ADD_REST"
+                                                icon={<PlusIcon/>}
+                                                onClick={e => 
this.createErrorHandlerErrorHandle()}>Create new error handler
+                                            </Button>}
+                                        </div>
+                                    </div>
+                                </div>
+                            </DrawerContentBody>
+                        </DrawerContent>
+                    </Drawer>
+                </div>
+                {this.getDeleteConfirmation()}
+            </PageSection>
+        )
+    }
+}
diff --git a/karavan-app/src/main/webui/src/designer/karavan.css 
b/karavan-app/src/main/webui/src/designer/karavan.css
index 17b9f6b..2ae90aa 100644
--- a/karavan-app/src/main/webui/src/designer/karavan.css
+++ b/karavan-app/src/main/webui/src/designer/karavan.css
@@ -468,7 +468,7 @@
     width: 350px;
 }
 
-.karavan .properties .add-button {
+.karavan .properties .change-button {
     font-size: 15px;
     height: 15px;
     line-height: 1;
@@ -478,10 +478,18 @@
     background: transparent;
 }
 
-.karavan .properties .add-button svg {
+.karavan .properties .change-button svg {
     margin-right: 6px;
 }
 
+.karavan .properties .add-button {
+    color: var(--pf-global--active-color--100);
+}
+
+.karavan .properties .delete-button {
+    color:  #909090;
+}
+
 .karavan .properties .pf-c-expandable-section__toggle {
     margin: 0;
     padding: 0;
diff --git a/karavan-app/src/main/webui/src/designer/route/RouteDesigner.tsx 
b/karavan-app/src/main/webui/src/designer/route/RouteDesigner.tsx
index 14ded06..6c976a9 100644
--- a/karavan-app/src/main/webui/src/designer/route/RouteDesigner.tsx
+++ b/karavan-app/src/main/webui/src/designer/route/RouteDesigner.tsx
@@ -40,6 +40,7 @@ import {CamelUi, RouteToCreate} from "../utils/CamelUi";
 import {findDOMNode} from "react-dom";
 import {CamelDisplayUtil} from "karavan-core/lib/api/CamelDisplayUtil";
 import {toPng} from 'html-to-image';
+import {KaravanDesigner} from "../KaravanDesigner";
 
 interface Props {
     onSave?: (integration: Integration, propertyOnly: boolean) => void
diff --git 
a/karavan-app/src/main/webui/src/designer/route/property/DslPropertyField.tsx 
b/karavan-app/src/main/webui/src/designer/route/property/DslPropertyField.tsx
index 9f4504e..369f4ee 100644
--- 
a/karavan-app/src/main/webui/src/designer/route/property/DslPropertyField.tsx
+++ 
b/karavan-app/src/main/webui/src/designer/route/property/DslPropertyField.tsx
@@ -36,8 +36,6 @@ import {
     Tooltip,
     Card,
     InputGroup,
-    Modal,
-    ModalVariant, Title, TitleSizes
 } from '@patternfly/react-core';
 import '../../karavan.css';
 import "@patternfly/patternfly/patternfly.css";
@@ -64,8 +62,10 @@ import ExpandIcon from 
"@patternfly/react-icons/dist/js/icons/expand-icon";
 import KubernetesIcon from 
"@patternfly/react-icons/dist/js/icons/openshift-icon";
 import {KubernetesSelector} from "./KubernetesSelector";
 import {KubernetesAPI} from "../../utils/KubernetesAPI";
-import Editor from "@monaco-editor/react";
 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";
 
 interface Props {
     property: PropertyMeta,
@@ -88,6 +88,7 @@ interface State {
     showEditor: boolean
     showKubernetesSelector: boolean
     kubernetesSelectorProperty?: string
+    customCode?: string
     ref: any
 }
 
@@ -148,13 +149,14 @@ export class DslPropertyField extends 
React.Component<Props, State> {
     getLabel = (property: PropertyMeta, value: any) => {
         if (!this.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 noVerticalAlign/>) : (<AddIcon 
noVerticalAlign/>);
             return (
                 <div style={{display: "flex"}}>
                     <Text>{property.displayName} </Text>
-                    <Tooltip position={"bottom"} 
content={<div>{tooltip}</div>}>
-                        <button className="add-button" onClick={e => 
this.props.onChange?.call(this, property.name, x)} aria-label="Add element">
+                    <Tooltip position={"top"} content={<div>{tooltip}</div>}>
+                        <button className={className} onClick={e => 
this.props.onChange?.call(this, property.name, x)} aria-label="Add element">
                             {icon}
                         </button>
                     </Tooltip>
@@ -243,9 +245,51 @@ export class DslPropertyField extends 
React.Component<Props, State> {
         </InputGroup>)
     }
 
+    showCode = (name: string) => {
+        const {property} = this.props;
+        KaravanInstance.getProps().onGetCustomCode.call(this, name).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))
+    }
+
+    getJavaTypeGeneratedInput = (property: PropertyMeta, value: any) => {
+        const {dslLanguage, dark} = this.props;
+        const {showEditor, customCode} = this.state;
+        return (<InputGroup>
+            <TextInput
+                ref={this.state.ref}
+                className="text-field" isRequired 
isReadOnly={this.isUriReadOnly(property)}
+                type="text"
+                id={property.name} name={property.name}
+                value={value?.toString()}
+                onChange={e => this.propertyChanged(property.name, 
CamelUtil.capitalizeName(e?.replace(/\s/g, '')))}/>
+            <Tooltip position="bottom-end" content={"Create Java Class"}>
+                <Button variant="control" onClick={e => this.showCode(value)}>
+                    <PlusIcon/>
+                </Button>
+            </Tooltip>
+                <ModalEditor property={property}
+                             value={customCode}
+                             showEditor={showEditor}
+                             dark={dark}
+                             dslLanguage={dslLanguage}
+                             title="Java Class"
+                             onSave={(fieldId, value1) => {
+                                 this.propertyChanged(fieldId, value);
+                                 
KaravanInstance.getProps().onSaveCustomCode?.call(this, value, value1);
+                                 this.setState({showEditor: false});
+                             }}/>
+        </InputGroup>)
+    }
+
     getTextArea = (property: PropertyMeta, value: any) => {
         const {dslLanguage, dark} = this.props;
-        const showEditor = this.state.showEditor;
+        const {showEditor} = this.state;
         return (
             <InputGroup>
                 <TextArea
@@ -261,37 +305,16 @@ export class DslPropertyField extends 
React.Component<Props, State> {
                         <EditorIcon/>
                     </Button>
                 </Tooltip>
-                <Modal
-                    variant={ModalVariant.large}
-                    header={<React.Fragment>
-                        <Title id="modal-custom-header-label" 
headingLevel="h1" size={TitleSizes['2xl']}>
-                            {`Expression (${dslLanguage?.[0]})`}
-                        </Title>
-                        <p className="pf-u-pt-sm">{dslLanguage?.[2]}</p>
-                    </React.Fragment>}
-                    isOpen={this.state.showEditor}
-                    onClose={() => this.setState({showEditor: false})}
-                    actions={[
-                        <Button key="cancel" variant="primary" isSmall
-                                onClick={e => this.setState({showEditor: 
false})}>Close</Button>
-                    ]}
-                    onEscapePress={e => this.setState({showEditor: false})}>
-                    <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={value?.toString()}
-                        className={'code-editor'}
-                        onChange={(value: any, ev: any) => {
-                            if (value) {
-                                this.propertyChanged(property.name, value)
-                            }
-                        }}
-                    />
-                </Modal>
+                <ModalEditor property={property}
+                             value={value}
+                             showEditor={showEditor}
+                             dark={dark}
+                             dslLanguage={dslLanguage}
+                             title={`Expression (${dslLanguage?.[0]})`}
+                             onSave={(fieldId, value1) => {
+                                 this.propertyChanged(fieldId, value1);
+                                 this.setState({showEditor: false});
+                             }}/>
             </InputGroup>
         )
     }
@@ -433,6 +456,10 @@ export class DslPropertyField extends 
React.Component<Props, State> {
         }
     }
 
+    javaTypeGenerated = (property: PropertyMeta): boolean => {
+        return property.javaType.length !== 0;
+    }
+
     getInternalUriSelect = (property: PropertyMeta, value: any) => {
         const selectOptions: JSX.Element[] = [];
         const urls = CamelUi.getInternalRouteUris(this.props.integration, 
"direct");
@@ -672,10 +699,13 @@ export class DslPropertyField extends 
React.Component<Props, State> {
                         && 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)}
diff --git 
a/karavan-app/src/main/webui/src/designer/route/property/ModalEditor.tsx 
b/karavan-app/src/main/webui/src/designer/route/property/ModalEditor.tsx
new file mode 100644
index 0000000..a12242a
--- /dev/null
+++ b/karavan-app/src/main/webui/src/designer/route/property/ModalEditor.tsx
@@ -0,0 +1,98 @@
+/*
+ * 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,
+    ModalVariant, Title, TitleSizes
+} from '@patternfly/react-core';
+import '../../karavan.css';
+import "@patternfly/patternfly/patternfly.css";
+import {PropertyMeta} from "karavan-core/lib/model/CamelMetadata";
+import Editor from "@monaco-editor/react";
+
+interface Props {
+    property: PropertyMeta,
+    value: any,
+    onSave?: (fieldId: string, value: string | number | boolean | any) => void,
+    title: string,
+    dslLanguage?: [string, string, string],
+    dark: boolean
+    showEditor: boolean
+}
+
+interface State {
+    value: any,
+}
+
+export class ModalEditor extends React.Component<Props, State> {
+
+    public state: State = {
+        value: this.props.value,
+    }
+
+    componentDidUpdate = (prevProps: Readonly<Props>, prevState: 
Readonly<State>, snapshot?: any) => {
+        if (prevProps.showEditor !== this.props.showEditor) {
+            this.setState({value: this.props.value})
+        }
+    }
+
+    close(){
+        this.props.onSave?.call(this, this.props.property.name, 
this.props.value);
+    }
+
+    closeAndSave(){
+        this.props.onSave?.call(this, this.props.property.name, 
this.state.value);
+    }
+
+    render() {
+        const {dark, dslLanguage, title, showEditor} = this.props;
+        const {value} = 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-u-pt-sm">{dslLanguage?.[2]}</p>
+                </React.Fragment>}
+                isOpen={showEditor}
+                onClose={() => this.close()}
+                actions={[
+                    <Button key="save" variant="primary" isSmall
+                            onClick={e => this.closeAndSave()}>Save</Button>,
+                    <Button key="cancel" variant="secondary" isSmall
+                            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={value?.toString()}
+                    className={'code-editor'}
+                    onChange={(value: any, ev: any) => this.setState({value: 
value})}
+                />
+            </Modal>
+        )
+    }
+}
diff --git a/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx 
b/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx
index 5c98f13..51f8517 100644
--- a/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx
+++ b/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx
@@ -21,9 +21,9 @@ import {ComponentApi} from 
"karavan-core/lib/api/ComponentApi";
 import {CamelMetadataApi} from "karavan-core/lib/model/CamelMetadata";
 import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
 import {CamelDefinitionApiExt} from 
"karavan-core/lib/api/CamelDefinitionApiExt";
-import {NamedBeanDefinition, RouteDefinition, SagaDefinition, ToDefinition} 
from "karavan-core/lib/model/CamelDefinition";
+import {ErrorHandlerDefinition, NamedBeanDefinition, RouteDefinition, 
SagaDefinition, ToDefinition} from "karavan-core/lib/model/CamelDefinition";
 import {CamelElement, Integration} from 
"karavan-core/lib/model/IntegrationDefinition";
-import {AggregateIcon, ChoiceIcon, FilterIcon, SagaIcon, SortIcon, SplitIcon, 
TransformIcon} from "./KaravanIcons";
+import {AggregateIcon, ChoiceIcon, FilterIcon, SagaIcon, SortIcon, SplitIcon} 
from "./KaravanIcons";
 import React from "react";
 
 const StepElements: string[] = [
@@ -35,27 +35,34 @@ const StepElements: string[] = [
     "ConvertBodyDefinition",
     "DynamicRouterDefinition",
     "EnrichDefinition",
-    // "ErrorHandlerBuilderDeserializer",
     // "ErrorHandlerDefinition",
     "FilterDefinition",
+    "IdempotentConsumerDefinition",
     "LogDefinition",
     "LoopDefinition",
     "MarshalDefinition",
     "MulticastDefinition",
+    "PausableDefinition",
     "PollEnrichDefinition",
     "ProcessDefinition",
     "RecipientListDefinition",
     "RemoveHeaderDefinition",
     "RemoveHeadersDefinition",
+    "RemovePropertiesDefinition",
     "RemovePropertyDefinition",
+    "ResumableDefinition",
     "ResequenceDefinition",
+    "RoutingSlipDefinition",
+    "SamplingDefinition",
     "SagaDefinition",
     "SetBodyDefinition",
     "SetHeaderDefinition",
     "SetPropertyDefinition",
     "SortDefinition",
+    "ScriptDefinition",
     "SplitDefinition",
     "StepDefinition",
+    "StopDefinition",
     "ThreadsDefinition",
     "ThrottleDefinition",
     "ThrowExceptionDefinition",
@@ -380,6 +387,8 @@ export class CamelUi {
                 return "data:image/svg+xml,%3Csvg width='32' height='32' 
xmlns='http://www.w3.org/2000/svg' 
xmlns:svg='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3Cstyle%3E.cls-1 %7B fill: 
none; %7D%3C/style%3E%3C/defs%3E%3Cg class='layer'%3E%3Ctitle%3ELayer 
1%3C/title%3E%3Cpath d='m24,30l-20,0a2.0021,2.0021 0 0 1 
-2,-2l0,-6a2.0021,2.0021 0 0 1 2,-2l20,0a2.0021,2.0021 0 0 1 
2,2l0,6a2.0021,2.0021 0 0 1 -2,2zm-20,-8l-0.0015,0l0.0015,6l20,0l0,-6l-20,0z' 
id='svg_1'/%3E%3Cpolygon id='svg_2' poi [...]
             case "RemoveHeadersDefinition":
                 return "data:image/svg+xml,%3Csvg width='32' height='32' 
xmlns='http://www.w3.org/2000/svg' 
xmlns:svg='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3Cstyle%3E.cls-1 %7B fill: 
none; %7D%3C/style%3E%3C/defs%3E%3Cg class='layer'%3E%3Ctitle%3ELayer 
1%3C/title%3E%3Cpolygon id='svg_2' points='32.12467002868652,6.015733242034912 
32.12467002868652,4.021692276000977 27.047643661499023,4.021692276000977 
27.047643661499023,-1.0553351640701294 25.05360221862793,-1.0553350448608398 
25.053 [...]
+            case "RemovePropertiesDefinition":
+                return "data:image/svg+xml,%3Csvg width='32' height='32' 
xmlns='http://www.w3.org/2000/svg' 
xmlns:svg='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3Cstyle%3E.cls-1 %7B fill: 
none; %7D%3C/style%3E%3C/defs%3E%3Cg class='layer'%3E%3Ctitle%3ELayer 
1%3C/title%3E%3Cpolygon id='svg_2' points='32.12467002868652,6.015733242034912 
32.12467002868652,4.021692276000977 27.047643661499023,4.021692276000977 
27.047643661499023,-1.0553351640701294 25.05360221862793,-1.0553350448608398 
25.053 [...]
             case "SetHeaderDefinition":
                 return "data:image/svg+xml,%3Csvg width='32' height='32' 
xmlns='http://www.w3.org/2000/svg' 
xmlns:svg='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3Cstyle%3E.cls-1 %7B fill: 
none; %7D%3C/style%3E%3C/defs%3E%3Cg class='layer'%3E%3Ctitle%3ELayer 
1%3C/title%3E%3Cpath d='m24,30l-20,0a2.0021,2.0021 0 0 1 
-2,-2l0,-6a2.0021,2.0021 0 0 1 2,-2l20,0a2.0021,2.0021 0 0 1 
2,2l0,6a2.0021,2.0021 0 0 1 -2,2zm-20,-8l-0.0015,0l0.0015,6l20,0l0,-6l-20,0z' 
id='svg_1'/%3E%3Cpolygon id='svg_2' poi [...]
             case "SetPropertyDefinition":
@@ -395,7 +404,7 @@ export class CamelUi {
             case "ConvertBodyDefinition":
                 return "data:image/svg+xml,%3Csvg width='32' height='32' 
xmlns='http://www.w3.org/2000/svg' 
xmlns:svg='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3Cstyle%3E.cls-1 %7B fill: 
none; %7D%3C/style%3E%3C/defs%3E%3Cg class='layer'%3E%3Ctitle%3ELayer 
1%3C/title%3E%3Cpath d='m16,28l-8,0l0,-24l8,0l0,6a2.0058,2.0058 0 0 0 
2,2l6,0l0,4l2,0l0,-6a0.9092,0.9092 0 0 0 -0.3,-0.7l-7,-7a0.9087,0.9087 0 0 0 
-0.7,-0.3l-10,0a2.0058,2.0058 0 0 0 -2,2l0,24a2.0058,2.0058 0 0 0 
2,2l8,0l0,-2zm2,-23.6l [...]
             case "TransformDefinition":
-                return "data:image/svg+xml,%0A%3Csvg width='32px' 
height='32px' viewBox='0 0 32 32' id='icon' 
xmlns='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3Cstyle%3E.cls-1%7Bfill:none;%7D%3C/style%3E%3C/defs%3E%3Ctitle%3Escript%3C/title%3E%3Cpolygon
 points='18.83 26 21.41 23.42 20 22 16 26 20 30 21.42 28.59 18.83 
26'/%3E%3Cpolygon points='27.17 26 24.59 28.58 26 30 30 26 26 22 24.58 23.41 
27.17 26'/%3E%3Cpath 
d='M14,28H8V4h8v6a2.0058,2.0058,0,0,0,2,2h6v6h2V10a.9092.9092,0,0,0-.3-.7l-7 
[...]
+                return 
"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzJweCIgaGVpZ2h0PSIzMnB4IiB2aWV3Qm94PSIwIDAgMzIgMzIiIGlkPSJpY29uIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxkZWZzPjxzdHlsZT4uY2xzLTF7ZmlsbDpub25lO308L3N0eWxlPjwvZGVmcz48dGl0bGU+ZGF0YS1zaGFyZTwvdGl0bGU+PHBhdGggZD0iTTUsMjVWMTUuODI4MWwtMy41ODU5LDMuNTg2TDAsMThsNi02LDYsNi0xLjQxNDEsMS40MTQxTDcsMTUuODI4MVYyNUgxOXYySDdBMi4wMDI0LDIuMDAyNCwwLDAsMSw1LDI1WiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCAwKSIvPjxwYXRoIGQ9Ik0yNCwyMmg0YTIuMDAyLD
 [...]
             case "EnrichDefinition":
                 return "data:image/svg+xml,%3Csvg width='32' height='32' 
xmlns='http://www.w3.org/2000/svg' 
xmlns:svg='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3Cstyle%3E.cls-1 %7B fill: 
none; %7D%3C/style%3E%3C/defs%3E%3Cg class='layer'%3E%3Ctitle%3ELayer 
1%3C/title%3E%3Cpolygon id='svg_1' points='4,20 4,22 8.586000442504883,22 
2,28.586000442504883 3.4140000343322754,30 10,23.413999557495117 10,28 12,28 
12,20 4,20 '/%3E%3Cpath d='m25.7,9.3l-7,-7a0.9087,0.9087 0 0 0 
-0.7,-0.3l-10,0a2.005 [...]
             case "PollEnrichDefinition":
@@ -426,6 +435,24 @@ export class CamelUi {
                 return "data:image/svg+xml,%3Csvg 
xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' 
version='1.1' id='Layer_1' x='0px' y='0px' viewBox='0 0 305.001 305.001' 
style='enable-background:new 0 0 305.001 305.001;' xml:space='preserve'%3E%3Cg 
id='XMLID_7_'%3E%3Cpath id='XMLID_8_' 
d='M150.99,56.513c-14.093,9.912-30.066,21.147-38.624,39.734c-14.865,32.426,30.418,67.798,32.353,69.288
 c0.45,0.347,0.988,0.519,1.525,0.519c0.57,0,1.141-0.195,1.605-0.583c0.89 [...]
             case "ProcessDefinition":
                 return "data:image/svg+xml,%3Csvg 
xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' 
version='1.1' id='Capa_1' x='0px' y='0px' width='235.506px' height='235.506px' 
viewBox='0 0 235.506 235.506' style='enable-background:new 0 0 235.506 
235.506;' xml:space='preserve'%3E%3Cg%3E%3Cpath 
d='M234.099,29.368c-3.025-4.861-10.303-7.123-22.915-7.123c-13.492,0-28.304,2.661-28.625,2.733
 c-20.453,2.098-27.254,26.675-32.736,46.436c-1.924,6.969-3.755,13.549-5.8 [...]
+            case "ErrorHandlerDefinition":
+                return 
"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzJweCIgaGVpZ2h0PSIzMnB4IiB2aWV3Qm94PSIwIDAgMzIgMzIiIGlkPSJpY29uIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxkZWZzPjxzdHlsZT4uY2xzLTF7ZmlsbDpub25lO308L3N0eWxlPjwvZGVmcz48dGl0bGU+d2FybmluZzwvdGl0bGU+PHBhdGggZD0iTTE2LDJBMTQsMTQsMCwxLDAsMzAsMTYsMTQsMTQsMCwwLDAsMTYsMlptMCwyNkExMiwxMiwwLDEsMSwyOCwxNiwxMiwxMiwwLDAsMSwxNiwyOFoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAgMCkiLz48cmVjdCB4PSIxNSIgeT0iOCIgd2lkdGg9IjIiIGhlaWdodD0iMTEiLz48cG
 [...]
+            case "ScriptDefinition":
+                return 
"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjU2cHgiIGhlaWdodD0iMjU2cHgiIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBpZD0iRmxhdCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8cGF0aCBkPSJNNDMuMTc1MjksMTI4YTI5Ljc4NTIsMjkuNzg1MiwwLDAsMSw4LjAyMywxMC4yNTk3N0M1NiwxNDguMTYzMDksNTYsMTYwLjI4MTI1LDU2LDE3MmMwLDI0LjMxMzQ4LDEuMDE5NTMsMzYsMjQsMzZhOCw4LDAsMCwxLDAsMTZjLTE3LjQ4MTQ1LDAtMjkuMzI0MjItNi4xNDM1NS0zNS4xOTgyNC0xOC4yNTk3N0M0MCwxOTUuODM2OTEsNDAsMTgzLjcxODc1LDQwLDE3MmMwLTI0LjMxMzQ4LT
 [...]
+           case "PausableDefinition":
+                return 
"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzJweCIgaGVpZ2h0PSIzMnB4IiB2aWV3Qm94PSIwIDAgMzIgMzIiIGlkPSJpY29uIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxkZWZzPjxzdHlsZT4uY2xzLTF7ZmlsbDpub25lO308L3N0eWxlPjwvZGVmcz48dGl0bGU+cGF1c2UtLWZpbGxlZDwvdGl0bGU+PHBhdGggZD0iTTEyLDZIMTBBMiwyLDAsMCwwLDgsOFYyNGEyLDIsMCwwLDAsMiwyaDJhMiwyLDAsMCwwLDItMlY4YTIsMiwwLDAsMC0yLTJaIi8+PHBhdGggZD0iTTIyLDZIMjBhMiwyLDAsMCwwLTIsMlYyNGEyLDIsMCwwLDAsMiwyaDJhMiwyLDAsMCwwLDItMlY4YTIsMiwwLDAsMC
 [...]
+            case "StopDefinition":
+                return 
"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzJweCIgaGVpZ2h0PSIzMnB4IiB2aWV3Qm94PSIwIDAgMzIgMzIiIGlkPSJpY29uIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxkZWZzPjxzdHlsZT4uY2xzLTF7ZmlsbDpub25lO308L3N0eWxlPjwvZGVmcz48dGl0bGU+c3RvcC0tZmlsbGVkPC90aXRsZT48cGF0aCBkPSJNMjQsNkg4QTIsMiwwLDAsMCw2LDhWMjRhMiwyLDAsMCwwLDIsMkgyNGEyLDIsMCwwLDAsMi0yVjhhMiwyLDAsMCwwLTItMloiLz48cmVjdCBpZD0iX1RyYW5zcGFyZW50X1JlY3RhbmdsZV8iIGRhdGEtbmFtZT0iJmx0O1RyYW5zcGFyZW50IFJlY3RhbmdsZSZndDsiIG
 [...]
+            case "ResumableDefinition":
+                return 
"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTVweCIgaGVpZ2h0PSIxNXB4IiB2aWV3Qm94PSIwIDAgMTUgMTUiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGgKICAgIGZpbGwtcnVsZT0iZXZlbm9kZCIKICAgIGNsaXAtcnVsZT0iZXZlbm9kZCIKICAgIGQ9Ik0zLjA0OTk1IDIuNzQ5OTVDMy4wNDk5NSAyLjQ0NjE5IDIuODAzNzEgMi4xOTk5NSAyLjQ5OTk1IDIuMTk5OTVDMi4xOTYxOSAyLjE5OTk1IDEuOTQ5OTUgMi40NDYxOSAxLjk0OTk1IDIuNzQ5OTVWMTIuMjVDMS45NDk5NSAxMi41NTM3IDIuMTk2MTkgMTIuOCAyLjQ5OTk1IDEyLjhDMi44MDM3MS
 [...]
+            case "RoutingSlipDefinition":
+                return 
"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzJweCIgaGVpZ2h0PSIzMnB4IiB2aWV3Qm94PSIwIDAgMzIgMzIiIGlkPSJpY29uIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxkZWZzPjxzdHlsZT4uY2xzLTF7ZmlsbDpub25lO308L3N0eWxlPjwvZGVmcz48dGl0bGU+c2NoZW1hdGljczwvdGl0bGU+PHBhdGggZD0iTTI3LDE5LjAwMUE0LjAwNTYsNC4wMDU2LDAsMCwwLDIyLjk5OTEsMTVIOS4wMDExQTIuMDAzMSwyLjAwMzEsMCwwLDEsNywxMi45OTkxVjkuODU4QTMuOTk0OSwzLjk5NDksMCwwLDAsOS44NTgxLDdoMTIuMjg0YTQsNCwwLDEsMCwwLTJIOS44NTgxQTMuOTkxNiwzLjk5MT
 [...]
+            case "ClaimCheckDefinition":
+                return 
"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzJweCIgaGVpZ2h0PSIzMnB4IiB2aWV3Qm94PSIwIDAgMzIgMzIiIGlkPSJpY29uIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxkZWZzPjxzdHlsZT4uY2xzLTF7ZmlsbDpub25lO308L3N0eWxlPjwvZGVmcz48dGl0bGU+Y2VydGlmaWNhdGUtLWNoZWNrPC90aXRsZT48cmVjdCB4PSI2IiB5PSIxNiIgd2lkdGg9IjYiIGhlaWdodD0iMiIvPjxyZWN0IHg9IjYiIHk9IjEyIiB3aWR0aD0iMTAiIGhlaWdodD0iMiIvPjxyZWN0IHg9IjYiIHk9IjgiIHdpZHRoPSIxMCIgaGVpZ2h0PSIyIi8+PHBhdGggZD0iTTE0LDI2SDRWNkgyOFYxNmgyVjZhMi
 [...]
+            case "SamplingDefinition":
+                return 
"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pg0KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE5LjAuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPg0KPHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJMYXllcl8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCINCgkgdmlld0JveD0iMCAwIDUxMiA1MTIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDUxMiA1MT
 [...]
+            case "IdempotentConsumerDefinition":
+                return 
"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzJweCIgaGVpZ2h0PSIzMnB4IiB2aWV3Qm94PSIwIDAgMzIgMzIiIGlkPSJpY29uIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxkZWZzPjxzdHlsZT4uY2xzLTF7ZmlsbDpub25lO308L3N0eWxlPjwvZGVmcz48dGl0bGU+cmVzZWFyY2gtLW1hdHJpeDwvdGl0bGU+PHBvbHlnb24gcG9pbnRzPSIxOCAxMyAxOCA0IDE2IDQgMTYgNiAxMyA2IDEzIDggMTYgOCAxNiAxMyAxMyAxMyAxMyAxNSAyMSAxNSAyMSAxMyAxOCAxMyIvPjxwYXRoIGQ9Ik0xNi41LDIwQTMuNSwzLjUsMCwxLDEsMTMsMjMuNSwzLjUsMy41LDAsMCwxLDE2LjUsMjBtMC0yQT
 [...]
             default:
                 return camelIcon;
         }
@@ -460,7 +487,6 @@ export class CamelUi {
             case 'ChoiceDefinition' :return <ChoiceIcon/>;
             case 'SplitDefinition' :return <SplitIcon/>;
             case 'SagaDefinition' :return <SagaIcon/>;
-            case 'TransformDefinition' :return <TransformIcon/>;
             case 'FilterDefinition' :return <FilterIcon/>;
             case 'SortDefinition' :return <SortIcon/>;
             default: return 
this.getIconFromSource(CamelUi.getIconSrcForName(dslName))
@@ -486,6 +512,7 @@ export class CamelUi {
         const result = new Map<string, number>();
         result.set('routes', i.spec.flows?.filter((e: any) => e.dslName === 
'RouteDefinition').length || 0);
         result.set('rest', i.spec.flows?.filter((e: any) => e.dslName === 
'RestDefinition').length || 0);
+        result.set('error', i.spec.flows?.filter((e: any) => e.dslName === 
'ErrorHandlerDefinition').length || 0);
         const beans = i.spec.flows?.filter((e: any) => e.dslName === 'Beans');
         if (beans && beans.length > 0 && beans[0].beans && 
beans[0].beans.length > 0){
             result.set('beans', Array.from(beans[0].beans).length);
@@ -508,4 +535,9 @@ export class CamelUi {
         }
         return result;
     }
+
+    static getErrorHandler = (integration: Integration): 
ErrorHandlerDefinition | undefined => {
+        const errorHandler = integration.spec.flows?.filter((e: any) => 
e.dslName === 'ErrorHandlerDefinition').at(0);
+        return errorHandler;
+    }
 }
\ No newline at end of file
diff --git a/karavan-app/src/main/webui/src/projects/ProjectPage.tsx 
b/karavan-app/src/main/webui/src/projects/ProjectPage.tsx
index faf8d5c..f998586 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectPage.tsx
+++ b/karavan-app/src/main/webui/src/projects/ProjectPage.tsx
@@ -108,14 +108,16 @@ export class ProjectPage extends React.Component<Props, 
State> {
         return this.props.project.projectId === 'kamelets';
     }
 
-    isTemplatesProject():boolean {
-        return this.props.project.projectId === 'templates';
-    }
-
     post = (file: ProjectFile) => {
         KaravanApi.postProjectFile(file, res => {
             if (res.status === 200) {
-                // console.log(res) //TODO show notification
+                const newFile = res.data;
+                this.setState((state => {
+                    const index = state.files.findIndex(f => f.name === 
newFile.name);
+                    if (index !== -1) state.files.splice(index, 1, newFile)
+                    else state.files.push(newFile);
+                    return state
+                }))
             } else {
                 // console.log(res) //TODO show notification
             }
@@ -191,7 +193,10 @@ export class ProjectPage extends React.Component<Props, 
State> {
             {isFile &&
                 <div>
                     <Breadcrumb>
-                        <BreadcrumbItem to="#" onClick={event => 
this.setState({file: undefined})}>
+                        <BreadcrumbItem to="#" onClick={event => {
+                            this.setState({file: undefined})
+                            this.onRefresh();
+                        }}>
                             {"Project: " + project?.projectId}
                         </BreadcrumbItem>
                         <Badge>{getProjectFileType(file)}</Badge>
@@ -236,7 +241,8 @@ export class ProjectPage extends React.Component<Props, 
State> {
     }
 
     getDesigner = () => {
-        const file = this.state.file;
+        const {file, files} = this.state;
+        const {project} = this.props;
         return (
             file !== undefined &&
             <KaravanDesigner
@@ -246,6 +252,10 @@ export class ProjectPage extends React.Component<Props, 
State> {
                 filename={file.name}
                 yaml={file.code}
                 onSave={(name, yaml) => this.save(name, yaml)}
+                onSaveCustomCode={(name, code) => this.post(new 
ProjectFile(name+".java", project.projectId, code, Date.now()))}
+                onGetCustomCode={name => {
+                    return new Promise<string | undefined>(resolve => 
resolve(files.filter(f => f.name === name + ".java")?.at(0)?.code))
+                }}
             />
         )
     }
diff --git a/karavan-app/src/main/webui/src/projects/ProjectPageToolbar.tsx 
b/karavan-app/src/main/webui/src/projects/ProjectPageToolbar.tsx
index 19550c4..6314a50 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectPageToolbar.tsx
+++ b/karavan-app/src/main/webui/src/projects/ProjectPageToolbar.tsx
@@ -62,7 +62,7 @@ export class ProjectPageToolbar extends 
React.Component<Props> {
     }
 
     getTemplatesToolbar() {
-        const {project, file, editAdvancedProperties} = this.props;
+        const {file, editAdvancedProperties} = this.props;
         const {isPushing} = this.state;
         const isProperties = file !== undefined && 
file.name.endsWith("properties");
         return <Toolbar id="toolbar-group-types">
diff --git a/karavan-designer/src/designer/route/property/DslPropertyField.tsx 
b/karavan-designer/src/designer/route/property/DslPropertyField.tsx
index 369f4ee..773b36d 100644
--- a/karavan-designer/src/designer/route/property/DslPropertyField.tsx
+++ b/karavan-designer/src/designer/route/property/DslPropertyField.tsx
@@ -273,17 +273,17 @@ export class DslPropertyField extends 
React.Component<Props, State> {
                     <PlusIcon/>
                 </Button>
             </Tooltip>
-                <ModalEditor property={property}
-                             value={customCode}
-                             showEditor={showEditor}
-                             dark={dark}
-                             dslLanguage={dslLanguage}
-                             title="Java Class"
-                             onSave={(fieldId, value1) => {
-                                 this.propertyChanged(fieldId, value);
-                                 
KaravanInstance.getProps().onSaveCustomCode?.call(this, value, value1);
-                                 this.setState({showEditor: false});
-                             }}/>
+            <ModalEditor property={property}
+                         value={customCode}
+                         showEditor={showEditor}
+                         dark={dark}
+                         dslLanguage={dslLanguage}
+                         title="Java Class"
+                         onSave={(fieldId, value1) => {
+                             this.propertyChanged(fieldId, value);
+                             
KaravanInstance.getProps().onSaveCustomCode?.call(this, value, value1);
+                             this.setState({showEditor: false});
+                         }}/>
         </InputGroup>)
     }
 

Reply via email to