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 2021/12/05 21:00:00 UTC
[camel-karavan] branch main updated: Properties UI decomposition (#140)
This is an automated email from the ASF dual-hosted git repository.
marat pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-karavan.git
The following commit(s) were added to refs/heads/main by this push:
new 5aa32bf Properties UI decomposition (#140)
5aa32bf is described below
commit 5aa32bf85aafdc1875ecc9824677ba4fa378dbf7
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Sun Dec 5 15:59:57 2021 -0500
Properties UI decomposition (#140)
---
karavan-designer/src/App.tsx | 5 +-
karavan-designer/src/designer/api/CamelApiExt.tsx | 16 +-
karavan-designer/src/designer/api/CamelUi.tsx | 12 +-
karavan-designer/src/designer/ui/DslProperties.tsx | 243 ++-------------------
.../designer/ui/field/ComponentParameterField.tsx | 120 ++++++++++
.../src/designer/ui/field/DataFormatField.tsx | 124 +++++++++++
.../src/designer/ui/field/DslPropertyField.tsx | 176 +++++++++++++++
.../src/designer/ui/field/ExpressionField.tsx | 115 ++++++++++
.../src/designer/ui/field/KameletPropertyField.tsx | 99 +++++++++
9 files changed, 667 insertions(+), 243 deletions(-)
diff --git a/karavan-designer/src/App.tsx b/karavan-designer/src/App.tsx
index 4d3f39e..df7f5cf 100644
--- a/karavan-designer/src/App.tsx
+++ b/karavan-designer/src/App.tsx
@@ -53,7 +53,10 @@ class App extends React.Component<Props, State> {
' - pollEnrich:\n' +
' expression: {}\n' +
' - to: \n' +
- ' uri: "log:info"\n' +
+ ' uri: "log:info:xxx"\n' +
+ ' parameters:\n' +
+ ' level: \'OFF\'\n' +
+ ' logMask: true \n' +
' - choice:\n' +
' otherwise: {}\n' +
' when:\n' +
diff --git a/karavan-designer/src/designer/api/CamelApiExt.tsx b/karavan-designer/src/designer/api/CamelApiExt.tsx
index 58a5143..5c1eec7 100644
--- a/karavan-designer/src/designer/api/CamelApiExt.tsx
+++ b/karavan-designer/src/designer/api/CamelApiExt.tsx
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {CamelElement, FromStep, Integration, ProcessorStep} from "../model/CamelModel";
+import {CamelElement, Expression, FromStep, Integration, ProcessorStep} from "../model/CamelModel";
import {CamelMetadataApi, DataFormats, PropertyMeta} from "./CamelMetadata";
import {CamelApi} from "./CamelApi";
import {ComponentApi} from "./ComponentApi";
@@ -63,10 +63,10 @@ export class CamelApiExt {
return integration;
}
- static getExpressionLanguage = (element: CamelElement | undefined): string | undefined => {
- const el: any = Object.assign({}, element);
- if (el.hasOwnProperty('expression') && el.expression) {
- return el.expression.language
+ static getExpressionLanguage = (expression: Expression | undefined): string | undefined => {
+ const el: any = Object.assign({}, expression);
+ if (el.hasOwnProperty('language') && el.language) {
+ return el.language
} else {
return undefined;
}
@@ -78,10 +78,10 @@ export class CamelApiExt {
return dataFormat ? [dataFormat, el[dataFormat]] : undefined;
}
- static getExpressionValue = (element: CamelElement | undefined): string | undefined => {
- const language = CamelApiExt.getExpressionLanguage(element);
+ static getExpressionValue = (expression: Expression | undefined): string | undefined => {
+ const language = CamelApiExt.getExpressionLanguage(expression);
if (language) {
- return (element as any).expression[language];
+ return (expression as any)[language];
} else {
return undefined;
}
diff --git a/karavan-designer/src/designer/api/CamelUi.tsx b/karavan-designer/src/designer/api/CamelUi.tsx
index 79c19de..6d6be18 100644
--- a/karavan-designer/src/designer/api/CamelUi.tsx
+++ b/karavan-designer/src/designer/api/CamelUi.tsx
@@ -240,8 +240,11 @@ export class CamelUi {
};
static isShowExpressionTooltip = (element: CamelElement): boolean => {
- const exp = CamelApiExt.getExpressionValue(element);
- return element.hasOwnProperty("expression") && (exp !== undefined && exp?.trim().length > 0);
+ if (element.hasOwnProperty("expression")){
+ const exp = CamelApiExt.getExpressionValue((element as any).expression);
+ return (exp !== undefined && exp?.trim().length > 0);
+ }
+ return false;
}
static isShowUriTooltip = (element: CamelElement): boolean => {
@@ -250,8 +253,9 @@ export class CamelUi {
}
static getExpressionTooltip = (element: CamelElement): string => {
- const language = CamelApiExt.getExpressionLanguage(element) || 'simple';
- const value = CamelApiExt.getExpressionValue(element) || '';
+ const e = (element as any).expression;
+ const language = CamelApiExt.getExpressionLanguage(e) || 'simple';
+ const value = CamelApiExt.getExpressionValue(e) || '';
return language.concat(": ", value);
}
diff --git a/karavan-designer/src/designer/ui/DslProperties.tsx b/karavan-designer/src/designer/ui/DslProperties.tsx
index 80cd53d..c06f74a 100644
--- a/karavan-designer/src/designer/ui/DslProperties.tsx
+++ b/karavan-designer/src/designer/ui/DslProperties.tsx
@@ -24,12 +24,6 @@ import {
Popover,
Switch,
TextVariants,
- Select,
- SelectVariant,
- SelectDirection,
- SelectOption,
- TextArea,
- ExpandableSection,
} from '@patternfly/react-core';
import '../karavan.css';
import "@patternfly/patternfly/patternfly.css";
@@ -38,11 +32,12 @@ import {Property} from "../model/KameletModels";
import {CamelElement, Expression, Integration} from "../model/CamelModel";
import {CamelApi} from "../api/CamelApi";
import {CamelApiExt} from "../api/CamelApiExt";
-import {CamelMetadataApi, DataFormats, Languages, PropertyMeta} from "../api/CamelMetadata";
+import {CamelMetadataApi, PropertyMeta} from "../api/CamelMetadata";
import {CamelYaml} from "../api/CamelYaml";
import {CamelUi} from "../api/CamelUi";
import {ComponentApi} from "../api/ComponentApi";
-import {ComponentProperty} from "../model/ComponentModels";
+import {DslPropertyField} from "./field/DslPropertyField";
+import {DataFormatField} from "./field/DataFormatField";
interface Props {
integration: Integration,
@@ -114,7 +109,7 @@ export class DslProperties extends React.Component<Props, State> {
this.props.onPropertyUpdate?.call(this, clone, this.state.step.uuid);
}
}
- };
+ }
componentDidUpdate = (prevProps: Readonly<Props>, prevState: Readonly<State>, snapshot?: any) => {
if (prevProps.step !== this.props.step) {
@@ -130,14 +125,6 @@ export class DslProperties extends React.Component<Props, State> {
});
}
- openSelect = (propertyName: string) => {
- this.setState({selectStatus: new Map<string, boolean>([[propertyName, true]])});
- }
-
- isSelectOpen = (propertyName: string): boolean => {
- return this.state.selectStatus.has(propertyName) && this.state.selectStatus.get(propertyName) === true;
- }
-
getIntegrationHeader = (): JSX.Element => {
return (
<div className="headers">
@@ -215,225 +202,21 @@ export class DslProperties extends React.Component<Props, State> {
)
}
- createComponentProperty = (property: ComponentProperty): JSX.Element => {
- const prefix = "parameters";
- const id = prefix + "-" + property.name;
- const value = CamelApiExt.getParametersValue(this.state.element, property.name, property.kind === 'path');
- const selectOptions: JSX.Element[] = []
- if (property.enum && property.enum.length > 0) {
- selectOptions.push(<SelectOption key={0} value={"Select ..."} isPlaceholder/>);
- property.enum.forEach(v => selectOptions.push(<SelectOption key={v} value={v}/>));
- }
- return (
- <FormGroup
- key={id}
- label={property.displayName}
- fieldId={id}
- isRequired={property.kind === 'path' || property.required}
- labelIcon={
- <Popover
- position={"left"}
- headerContent={property.displayName}
- bodyContent={property.description}
- footerContent={property.defaultValue !== undefined ? "Default: " + property.defaultValue : undefined}>
- <button type="button" aria-label="More info" onClick={e => e.preventDefault()}
- className="pf-c-form__group-label-help">
- <HelpIcon noVerticalAlign/>
- </button>
- </Popover>
- }>
- {['string', 'duration', 'integer', 'int', 'number'].includes(property.type) && property.enum === undefined &&
- <TextInput
- className="text-field" isRequired
- type={['integer', 'int', 'number'].includes(property.type) ? 'number' : (property.secret ? "password" : "text")}
- id={id} name={id}
- value={value !== undefined ? value : property.defaultValue}
- onChange={e => this.parametersChanged(property.name, ['integer', 'int', 'number'].includes(property.type) ? Number(e) : e, property.kind === 'path')}/>
- }
- {property.type === 'string' && property.enum &&
- <Select
- variant={SelectVariant.single}
- aria-label={property.name}
- onToggle={isExpanded => {
- this.openSelect(property.name)
- }}
- onSelect={(e, value, isPlaceholder) => this.parametersChanged(property.name, (!isPlaceholder ? value : undefined), property.kind === 'path')}
- selections={value !== undefined ? value.toString() : property.defaultValue}
- isOpen={this.isSelectOpen(property.name)}
- aria-labelledby={property.name}
- direction={SelectDirection.down}
- >
- {selectOptions}
- </Select>
- }
- {property.type === 'boolean' && <Switch
- id={id} name={id}
- value={value?.toString()}
- aria-label={id}
- isChecked={value !== undefined ? Boolean(value) === true : Boolean(property.defaultValue) === true}
- onChange={e => this.parametersChanged(property.name, !Boolean(value))}/>
- }
- </FormGroup>
- )
- }
-
- createExpressionProperty = (property: PropertyMeta): JSX.Element => {
- const prefix = "language";
- const language = CamelApiExt.getExpressionLanguage(this.state.element) || 'Simple'
- const dslLanguage = Languages.find((l: [string, string, string]) => l[0] === language);
- const value = language ? CamelApiExt.getExpressionValue(this.state.element) : undefined;
- 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>
- <FormGroup key={prefix + "-" + property.name} fieldId={property.name}>
- <Select
- variant={SelectVariant.typeahead}
- aria-label={property.name}
- onToggle={isExpanded => {
- this.openSelect(property.name)
- }}
- onSelect={(e, lang, isPlaceholder) => this.expressionChanged(lang.toString(), value)}
- selections={dslLanguage}
- isOpen={this.isSelectOpen(property.name)}
- aria-labelledby={property.name}
- direction={SelectDirection.down}
- >
- {selectOptions}
- </Select>
- </FormGroup>
- <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-c-form__group-label-help">
- <HelpIcon noVerticalAlign/>
- </button>
- </Popover> : <div></div>
- }>
- <TextArea
- autoResize
- className="text-field" isRequired
- type={"text"}
- id={property.name} name={property.name}
- height={"100px"}
- value={value?.toString()}
- onChange={e => this.expressionChanged(language, e)}/>
- </FormGroup>
- </div>
- )
- }
-
- createEipDslProperty = (property: PropertyMeta): JSX.Element => {
- const value = this.state.element ? (this.state.element as any)[property.name] : undefined;
- const selectOptions: JSX.Element[] = []
- if (property.enumVals && property.enumVals.length > 0) {
- selectOptions.push(<SelectOption key={0} value={"Select " + property.name} isPlaceholder/>);
- selectOptions.push(...property.enumVals.split(',').map((value: string) =>
- <SelectOption key={value} value={value.trim()}/>));
- }
- return (
- <FormGroup
- key={property.name}
- label={CamelApi.capitalizeName(property.displayName)}
- fieldId={property.name}
- labelIcon={property.description ?
- <Popover
- position={"left"}
- headerContent={property.displayName}
- bodyContent={property.description}
- footerContent={property.defaultValue !== undefined ? "Default: " + property.defaultValue : undefined}>
- <button type="button" aria-label="More info" onClick={e => {
- e.preventDefault();
- e.stopPropagation();
- }}
- className="pf-c-form__group-label-help">
- <HelpIcon noVerticalAlign/>
- </button>
- </Popover> : <div></div>
- }>
- {['string', 'duration', 'integer', 'number'].includes(property.type) && !property.enumVals &&
- <TextInput
- isReadOnly={property.name === 'uri' && this.state.element?.dslName !== 'toD'}
- 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={e => this.propertyChanged(property.name, ['integer', 'number'].includes(property.type) ? Number(e) : e)}/>
- }
- {property.type === 'boolean' && <Switch
- id={property.name} name={property.name}
- value={this.state.element?.toString()}
- aria-label={property.name}
- isChecked={Boolean(value) === true}
- onChange={e => this.propertyChanged(property.name, !Boolean(value))}/>
- }
-
- {property.enumVals &&
- <Select
- variant={SelectVariant.single}
- aria-label={property.name}
- onToggle={isExpanded => {
- this.openSelect(property.name)
- }}
- onSelect={(e, value, isPlaceholder) => this.propertyChanged(property.name, (!isPlaceholder ? value : undefined))}
- selections={value}
- isOpen={this.isSelectOpen(property.name)}
- aria-labelledby={property.name}
- direction={SelectDirection.down}
- >
- {selectOptions}
- </Select>
- }
- {property.name === 'expression' && property.type === "Expression" &&
- <div className="expression">
- {this.createExpressionProperty(property)}
- </div>
- }
- <div className="parameters">
- {property.name === 'parameters' && CamelUi.isKameletComponent(this.state.element)
- && CamelUi.getKameletProperties(this.state.element).map(kp => this.createKameletProperty(kp))}
-
- {property.name === 'parameters' && this.state.element && !CamelUi.isKameletComponent(this.state.element)
- && CamelUi.getComponentProperties(this.state.element, false).map(kp => this.createComponentProperty(kp))}
- </div>
- {property.name === 'parameters' && this.state.element && !CamelUi.isKameletComponent(this.state.element) && CamelUi.getComponentProperties(this.state.element, true).length > 0 && (
- <ExpandableSection
- toggleText={'Advanced parameters'}
- onToggle={isExpanded => this.setState({isShowAdvanced: !this.state.isShowAdvanced})}
- isExpanded={this.state.isShowAdvanced}>
- <div className="parameters">
- {CamelUi.getComponentProperties(this.state.element, true).map(kp => this.createComponentProperty(kp))}
- </div>
- </ExpandableSection>
- )}
- </FormGroup>
- )
- }
-
- setDataFormat = (dataFormat: string, props: any) => {
- console.log(dataFormat);
- console.log(props);
- }
-
render() {
return (
<div key={this.state.step ? this.state.step.uuid : 'integration'} className='properties'>
<Form autoComplete="off">
{this.state.element === undefined && this.getIntegrationHeader()}
{this.state.element && this.getComponentHeader()}
- {this.state.element && CamelApiExt.getElementProperties(this.state.element.dslName).map((property: PropertyMeta) => this.createEipDslProperty(property))}
+ {this.state.element && CamelApiExt.getElementProperties(this.state.element.dslName).map((property: PropertyMeta) =>
+ <DslPropertyField property={property}
+ element={this.state.element}
+ value={this.state.element ? (this.state.element as any)[property.name] : undefined}
+ onExpressionChange={this.expressionChanged}
+ onParameterChange={this.parametersChanged}
+ onChange={this.propertyChanged} />
+ )}
+ {this.state.element && ['marshal', 'unmarshal'].includes(this.state.element.dslName) && <DataFormatField element={this.state.element}/>}
</Form>
</div>
)
diff --git a/karavan-designer/src/designer/ui/field/ComponentParameterField.tsx b/karavan-designer/src/designer/ui/field/ComponentParameterField.tsx
new file mode 100644
index 0000000..f9b6e15
--- /dev/null
+++ b/karavan-designer/src/designer/ui/field/ComponentParameterField.tsx
@@ -0,0 +1,120 @@
+/*
+ * 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,
+ Popover,
+ Switch,
+ Select,
+ SelectVariant,
+ SelectDirection,
+ SelectOption,
+} from '@patternfly/react-core';
+import '../../karavan.css';
+import "@patternfly/patternfly/patternfly.css";
+import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon";
+import {ComponentProperty} from "../../model/ComponentModels";
+
+interface Props {
+ property: ComponentProperty,
+ value: any,
+ onParameterChange?: (parameter: string, value: string | number | boolean | any, pathParameter?: boolean) => void
+}
+
+interface State {
+ selectIsOpen: boolean
+}
+
+export class ComponentParameterField extends React.Component<Props, State> {
+
+ public state: State = {
+ selectIsOpen: false,
+ }
+
+ openSelect = () => {
+ this.setState({selectIsOpen: true});
+ }
+
+ parametersChanged = (parameter: string, value: string | number | boolean | any, pathParameter?: boolean) => {
+ this.props.onParameterChange?.call(this, parameter, value, pathParameter);
+ this.setState({selectIsOpen: false});
+ }
+
+ render() {
+ const property: ComponentProperty = this.props.property;
+ const value = this.props.value;
+ const prefix = "parameters";
+ const id = prefix + "-" + property.name;
+ const selectOptions: JSX.Element[] = []
+ if (property.enum && property.enum.length > 0) {
+ selectOptions.push(<SelectOption key={0} value={"Select ..."} isPlaceholder/>);
+ property.enum.forEach(v => selectOptions.push(<SelectOption key={v} value={v}/>));
+ }
+ return (
+ <FormGroup
+ key={id}
+ label={property.displayName}
+ fieldId={id}
+ isRequired={property.kind === 'path' || property.required}
+ labelIcon={
+ <Popover
+ position={"left"}
+ headerContent={property.displayName}
+ bodyContent={property.description}
+ footerContent={property.defaultValue !== undefined ? "Default: " + property.defaultValue : undefined}>
+ <button type="button" aria-label="More info" onClick={e => e.preventDefault()}
+ className="pf-c-form__group-label-help">
+ <HelpIcon noVerticalAlign/>
+ </button>
+ </Popover>
+ }>
+ {['string', 'duration', 'integer', 'int', 'number'].includes(property.type) && property.enum === undefined &&
+ <TextInput
+ className="text-field" isRequired
+ type={['integer', 'int', 'number'].includes(property.type) ? 'number' : (property.secret ? "password" : "text")}
+ id={id} name={id}
+ value={value !== undefined ? value : property.defaultValue}
+ onChange={e => this.parametersChanged(property.name, ['integer', 'int', 'number'].includes(property.type) ? Number(e) : e, property.kind === 'path')}/>
+ }
+ {property.type === 'string' && property.enum &&
+ <Select
+ variant={SelectVariant.single}
+ aria-label={property.name}
+ onToggle={isExpanded => {
+ this.openSelect()
+ }}
+ onSelect={(e, value, isPlaceholder) => this.parametersChanged(property.name, (!isPlaceholder ? value : undefined), property.kind === 'path')}
+ selections={value !== undefined ? value.toString() : property.defaultValue}
+ isOpen={this.state.selectIsOpen}
+ aria-labelledby={property.name}
+ direction={SelectDirection.down}
+ >
+ {selectOptions}
+ </Select>
+ }
+ {property.type === 'boolean' && <Switch
+ id={id} name={id}
+ value={value?.toString()}
+ aria-label={id}
+ isChecked={value !== undefined ? Boolean(value) === true : Boolean(property.defaultValue) === true}
+ onChange={e => this.parametersChanged(property.name, !Boolean(value))}/>
+ }
+ </FormGroup>
+ )
+ }
+}
\ No newline at end of file
diff --git a/karavan-designer/src/designer/ui/field/DataFormatField.tsx b/karavan-designer/src/designer/ui/field/DataFormatField.tsx
new file mode 100644
index 0000000..a4168f2
--- /dev/null
+++ b/karavan-designer/src/designer/ui/field/DataFormatField.tsx
@@ -0,0 +1,124 @@
+/*
+ * 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,
+ Popover,
+ Select,
+ SelectVariant,
+ SelectDirection,
+ SelectOption,
+ TextArea,
+} from '@patternfly/react-core';
+import '../../karavan.css';
+import "@patternfly/patternfly/patternfly.css";
+import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon";
+import {CamelElement} from "../../model/CamelModel";
+import {CamelApiExt} from "../../api/CamelApiExt";
+import {CamelMetadataApi, DataFormats, PropertyMeta} from "../../api/CamelMetadata";
+
+interface Props {
+ element: CamelElement,
+}
+
+interface State {
+ property: PropertyMeta,
+ element?: CamelElement,
+ selectStatus: Map<string, boolean>
+}
+
+export class DataFormatField extends React.Component<Props, State> {
+
+ public state: State = {
+ property: new PropertyMeta('id', 'Id', "The id of this node", 'string', '', '', false, false, false, false),
+ selectStatus: new Map<string, boolean>(),
+ element: this.props.element,
+ }
+
+ setDataFormat = (dataFormat: string, props: any) => {
+ console.log(dataFormat);
+ console.log(props);
+ }
+
+ openSelect = (propertyName: string) => {
+ this.setState({selectStatus: new Map<string, boolean>([[propertyName, true]])});
+ }
+
+ isSelectOpen = (propertyName: string): boolean => {
+ return this.state.selectStatus.has(propertyName) && this.state.selectStatus.get(propertyName) === true;
+ }
+
+ render() {
+ const fieldId = "dataFormat";
+ const dataFormat = CamelApiExt.getDataFormat(this.state.element)
+ const dataFormatName = dataFormat ? dataFormat[0] : '';
+ const value = dataFormat ? CamelApiExt.getExpressionValue(this.state.element) : undefined;
+ const properties = CamelMetadataApi.getCamelDataFormatMetadata(dataFormatName)?.properties;
+ const selectOptions: JSX.Element[] = []
+ DataFormats.forEach((df: [string, string, string]) => {
+ const s = <SelectOption key={df[0]} value={df[0]} description={df[2]}/>;
+ selectOptions.push(s);
+ })
+ return (
+ <div>
+ <FormGroup
+ key={fieldId}
+ label="Data Format"
+ fieldId="dataFormat"
+ labelIcon={
+ <Popover
+ position={"left"}
+ headerContent="Data Format"
+ bodyContent="Specified format for transmission over a transport or component">
+ <button type="button" aria-label="More info" onClick={e => {
+ e.preventDefault();
+ e.stopPropagation();
+ }}
+ className="pf-c-form__group-label-help">
+ <HelpIcon noVerticalAlign/>
+ </button>
+ </Popover>}
+ >
+ <div className="dataformat">
+ <Select
+ variant={SelectVariant.typeahead}
+ aria-label="dataFormat"
+ onToggle={isExpanded => {
+ this.openSelect(fieldId)
+ }}
+ onSelect={(e, df, isPlaceholder) => this.setDataFormat(df.toString(), value)}
+ selections={dataFormatName}
+ isOpen={this.isSelectOpen(fieldId)}
+ aria-labelledby={fieldId}
+ direction={SelectDirection.down}
+ >
+ {selectOptions}
+ </Select>
+ <TextArea
+ autoResize
+ className="text-field" isRequired
+ type={"text"}
+ id={fieldId+"text"} name={fieldId+"text"}
+ height={"100px"}
+ value={value?.toString()}
+ onChange={e => this.setDataFormat(dataFormatName, e)}/>
+ </div>
+ </FormGroup>
+ </div>
+ )
+ }
+}
\ No newline at end of file
diff --git a/karavan-designer/src/designer/ui/field/DslPropertyField.tsx b/karavan-designer/src/designer/ui/field/DslPropertyField.tsx
new file mode 100644
index 0000000..073907d
--- /dev/null
+++ b/karavan-designer/src/designer/ui/field/DslPropertyField.tsx
@@ -0,0 +1,176 @@
+/*
+ * 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,
+ Popover,
+ Switch,
+ Select,
+ SelectVariant,
+ SelectDirection,
+ SelectOption, ExpandableSection,
+} from '@patternfly/react-core';
+import '../../karavan.css';
+import "@patternfly/patternfly/patternfly.css";
+import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon";
+import {CamelApi} from "../../api/CamelApi";
+import { PropertyMeta} from "../../api/CamelMetadata";
+import {CamelApiExt} from "../../api/CamelApiExt";
+import {ExpressionField} from "./ExpressionField";
+import {CamelUi} from "../../api/CamelUi";
+import {ComponentParameterField} from "./ComponentParameterField";
+import {CamelElement} from "../../model/CamelModel";
+import {KameletPropertyField} from "./KameletPropertyField";
+
+interface Props {
+ property: PropertyMeta,
+ value: any,
+ onChange?: (fieldId: string, value: string | number | boolean | any) => void,
+ onExpressionChange?: (language: string, value: string | undefined) => void,
+ onParameterChange?: (parameter: string, value: string | number | boolean | any, pathParameter?: boolean) => void,
+ element?: CamelElement
+}
+
+interface State {
+ selectStatus: Map<string, boolean>,
+ isShowAdvanced: boolean
+}
+
+export class DslPropertyField extends React.Component<Props, State> {
+
+ public state: State = {
+ selectStatus: new Map<string, boolean>(),
+ isShowAdvanced: false
+ }
+
+ openSelect = (propertyName: string) => {
+ this.setState({selectStatus: new Map<string, boolean>([[propertyName, true]])});
+ }
+
+ isSelectOpen = (propertyName: string): boolean => {
+ return this.state.selectStatus.has(propertyName) && this.state.selectStatus.get(propertyName) === true;
+ }
+
+ propertyChanged = (fieldId: string, value: string | number | boolean | any) => {
+ this.props.onChange?.call(this, fieldId, value);
+ }
+
+ render() {
+ const isKamelet = CamelUi.isKameletComponent(this.props.element);
+ const property: PropertyMeta = this.props.property;
+ const value = this.props.value;
+ const selectOptions: JSX.Element[] = []
+ if (property.enumVals && property.enumVals.length > 0) {
+ selectOptions.push(<SelectOption key={0} value={"Select " + property.name} isPlaceholder/>);
+ selectOptions.push(...property.enumVals.split(',').map((value: string) =>
+ <SelectOption key={value} value={value.trim()}/>));
+ }
+ return (
+ <FormGroup
+ key={property.name}
+ label={CamelApi.capitalizeName(property.displayName)}
+ fieldId={property.name}
+ labelIcon={property.description ?
+ <Popover
+ position={"left"}
+ headerContent={property.displayName}
+ bodyContent={property.description}
+ footerContent={property.defaultValue !== undefined ? "Default: " + property.defaultValue : undefined}>
+ <button type="button" aria-label="More info" onClick={e => {
+ e.preventDefault();
+ e.stopPropagation();
+ }}
+ className="pf-c-form__group-label-help">
+ <HelpIcon noVerticalAlign/>
+ </button>
+ </Popover> : <div></div>
+ }>
+ {['string', 'duration', 'integer', 'number'].includes(property.type) && !property.enumVals &&
+ <TextInput
+ // isReadOnly={property.name === 'uri' && this.state.element?.dslName !== 'toD'}
+ 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={e => this.propertyChanged(property.name, ['integer', 'number'].includes(property.type) ? Number(e) : e)}/>
+ }
+ {property.type === 'boolean' && <Switch
+ id={property.name} name={property.name}
+ value={value?.toString()}
+ aria-label={property.name}
+ isChecked={Boolean(value) === true}
+ onChange={e => this.propertyChanged(property.name, !Boolean(value))}/>
+ }
+
+ {property.enumVals &&
+ <Select
+ variant={SelectVariant.single}
+ aria-label={property.name}
+ onToggle={isExpanded => {
+ this.openSelect(property.name)
+ }}
+ onSelect={(e, value, isPlaceholder) => this.propertyChanged(property.name, (!isPlaceholder ? value : undefined))}
+ selections={value}
+ isOpen={this.isSelectOpen(property.name)}
+ aria-labelledby={property.name}
+ direction={SelectDirection.down}
+ >
+ {selectOptions}
+ </Select>
+ }
+ {property.name === 'expression' && property.type === "Expression" &&
+ <div className="expression">
+ <ExpressionField property={property} value={value} onExpressionChange={this.props.onExpressionChange} />
+ </div>
+ }
+ {property.name === 'parameters' &&
+ <div className="parameters">
+ {!isKamelet && CamelUi.getComponentProperties(this.props.element, false).map(kp =>
+ <ComponentParameterField
+ property={kp}
+ value={CamelApiExt.getParametersValue(this.props.element, kp.name, kp.kind === 'path')}
+ onParameterChange={this.props.onParameterChange}
+ />)}
+ {isKamelet && CamelUi.getKameletProperties(this.props.element).map(property =>
+ <KameletPropertyField
+ property={property}
+ value={CamelApiExt.getParametersValue(this.props.element, property.id)}
+ onParameterChange={this.props.onParameterChange}
+ />)}
+ </div>
+ }
+ {property.name === 'parameters' && this.props.element && !isKamelet && CamelUi.getComponentProperties(this.props.element, true).length > 0 && (
+ <ExpandableSection
+ toggleText={'Advanced parameters'}
+ onToggle={isExpanded => this.setState({isShowAdvanced: !this.state.isShowAdvanced})}
+ isExpanded={this.state.isShowAdvanced}>
+ <div className="parameters">
+ {CamelUi.getComponentProperties(this.props.element, true).map(kp =>
+ <ComponentParameterField
+ property={kp}
+ value={CamelApiExt.getParametersValue(this.props.element, kp.name, kp.kind === 'path')}
+ onParameterChange={this.props.onParameterChange}
+ />
+ )}
+ </div>
+ </ExpandableSection>
+ )}
+ </FormGroup>
+ )
+ }
+}
\ No newline at end of file
diff --git a/karavan-designer/src/designer/ui/field/ExpressionField.tsx b/karavan-designer/src/designer/ui/field/ExpressionField.tsx
new file mode 100644
index 0000000..62eb70c
--- /dev/null
+++ b/karavan-designer/src/designer/ui/field/ExpressionField.tsx
@@ -0,0 +1,115 @@
+/*
+ * 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,
+ Popover,
+ Select,
+ SelectVariant,
+ SelectDirection,
+ SelectOption, TextArea,
+} from '@patternfly/react-core';
+import '../../karavan.css';
+import "@patternfly/patternfly/patternfly.css";
+import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon";
+import {Languages, PropertyMeta} from "../../api/CamelMetadata";
+import {CamelApiExt} from "../../api/CamelApiExt";
+
+interface Props {
+ property: PropertyMeta,
+ value: any,
+ onExpressionChange?: (language: string, value: string | undefined) => void
+}
+
+interface State {
+ selectIsOpen: boolean;
+}
+
+export class ExpressionField extends React.Component<Props, State> {
+
+ public state: State = {
+ selectIsOpen: false,
+ }
+
+ openSelect = () => {
+ this.setState({selectIsOpen: true});
+ }
+
+ expressionChanged = (language: string, value: string | undefined) => {
+ this.props.onExpressionChange?.call(this, language, value);
+ this.setState({selectIsOpen: false});
+ }
+
+ render() {
+ const property: PropertyMeta = this.props.property;
+ const prefix = "language";
+ const language = CamelApiExt.getExpressionLanguage(this.props.value) || 'Simple'
+ const dslLanguage = Languages.find((l: [string, string, string]) => l[0] === language);
+ const value = language ? CamelApiExt.getExpressionValue(this.props.value) : undefined;
+ 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>
+ <FormGroup key={prefix + "-" + property.name} fieldId={property.name}>
+ <Select
+ variant={SelectVariant.typeahead}
+ aria-label={property.name}
+ onToggle={isExpanded => {
+ this.openSelect()
+ }}
+ 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>
+ <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-c-form__group-label-help">
+ <HelpIcon noVerticalAlign/>
+ </button>
+ </Popover> : <div></div>
+ }>
+ <TextArea
+ autoResize
+ className="text-field" isRequired
+ type={"text"}
+ id={property.name} name={property.name}
+ height={"100px"}
+ value={value?.toString()}
+ onChange={e => this.expressionChanged(language, e)}/>
+ </FormGroup>
+ </div>
+ )
+ }
+}
\ No newline at end of file
diff --git a/karavan-designer/src/designer/ui/field/KameletPropertyField.tsx b/karavan-designer/src/designer/ui/field/KameletPropertyField.tsx
new file mode 100644
index 0000000..78ed047
--- /dev/null
+++ b/karavan-designer/src/designer/ui/field/KameletPropertyField.tsx
@@ -0,0 +1,99 @@
+/*
+ * 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,
+ Popover,
+ Switch,
+} from '@patternfly/react-core';
+import '../../karavan.css';
+import "@patternfly/patternfly/patternfly.css";
+import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon";
+import {Property} from "../../model/KameletModels";
+
+interface Props {
+ property: Property,
+ value: any,
+ onParameterChange?: (parameter: string, value: string | number | boolean | any, pathParameter?: boolean) => void
+}
+
+interface State {
+ selectIsOpen: boolean
+}
+
+export class KameletPropertyField extends React.Component<Props, State> {
+
+ public state: State = {
+ selectIsOpen: false,
+ }
+
+ openSelect = () => {
+ this.setState({selectIsOpen: true});
+ }
+
+ parametersChanged = (parameter: string, value: string | number | boolean | any, pathParameter?: boolean) => {
+ this.props.onParameterChange?.call(this, parameter, value, pathParameter);
+ this.setState({selectIsOpen: false});
+ }
+
+ render() {
+ const property = this.props.property;
+ const value = this.props.value;
+ const prefix = "parameters";
+ const id = prefix + "-" + property.id;
+ return (
+ <FormGroup
+ key={id}
+ label={property.title}
+ fieldId={id}
+ 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-c-form__group-label-help">
+ <HelpIcon noVerticalAlign/>
+ </button>
+ </Popover>
+ }>
+ {['string', 'integer', 'int', 'number'].includes(property.type) && <TextInput
+ className="text-field" isRequired
+ type={['integer', 'int', 'number'].includes(property.type) ? 'number' : (property.format ? "password" : "text")}
+ id={id} name={id}
+ value={value}
+ onChange={e => this.parametersChanged(property.id, ['integer', 'int', 'number'].includes(property.type) ? Number(e) : e)}/>
+ }
+ {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>
+ )
+ }
+}
\ No newline at end of file