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 083466d2 FIx #930 083466d2 is described below commit 083466d2a46d34bd02ce5a14ab44251028124d8a Author: Marat Gubaidullin <ma...@talismancloud.io> AuthorDate: Wed Oct 4 20:01:10 2023 -0400 FIx #930 --- .../designer/kamelet/KameletAnnotationsPanel.tsx | 3 +- karavan-designer/src/designer/karavan.css | 1 + .../route/property/ComponentParameterField.tsx | 4 +- .../designer/route/property/DslPropertyField.tsx | 12 +- .../designer/kamelet/KameletAnnotationsPanel.tsx | 3 +- .../kamelet/KameletDefinitionPropertyCard.tsx | 125 +++++++++++- .../designer/kamelet/KameletDefinitionsPanel.tsx | 59 +++++- .../designer/kamelet/KameletDependenciesCard.tsx | 113 +++++++++++ .../src/designer/kamelet/KameletProperties.tsx | 209 +-------------------- .../src/designer/kamelet/KameletTypesOutCard.tsx | 113 +++++++++++ karavan-space/src/designer/kamelet/kamelet.css | 1 + karavan-space/src/designer/karavan.css | 1 + .../route/property/ComponentParameterField.tsx | 4 +- .../designer/route/property/DslPropertyField.tsx | 12 +- .../designer/kamelet/KameletAnnotationsPanel.tsx | 3 +- .../src/main/webui/src/designer/karavan.css | 1 + .../route/property/ComponentParameterField.tsx | 4 +- .../designer/route/property/DslPropertyField.tsx | 12 +- 18 files changed, 434 insertions(+), 246 deletions(-) diff --git a/karavan-designer/src/designer/kamelet/KameletAnnotationsPanel.tsx b/karavan-designer/src/designer/kamelet/KameletAnnotationsPanel.tsx index 076aec40..b1a6b324 100644 --- a/karavan-designer/src/designer/kamelet/KameletAnnotationsPanel.tsx +++ b/karavan-designer/src/designer/kamelet/KameletAnnotationsPanel.tsx @@ -75,8 +75,7 @@ export function KameletAnnotationsPanel() { return ( <GridItem span={span}> <FormGroup label={label} fieldId={key} isRequired> - {/* eslint-disable-next-line react/jsx-no-undef */} - <ToggleGroup aria-label={key}> + <ToggleGroup aria-label={key} id={key} name={key}> {values.map(value => <ToggleGroupItem key={value} diff --git a/karavan-designer/src/designer/karavan.css b/karavan-designer/src/designer/karavan.css index fb04c267..31aa866b 100644 --- a/karavan-designer/src/designer/karavan.css +++ b/karavan-designer/src/designer/karavan.css @@ -343,6 +343,7 @@ flex-direction: column; justify-content: space-between; margin-bottom: 20px; + padding-bottom: 30px; } .karavan .pf-v5-c-drawer__splitter { diff --git a/karavan-designer/src/designer/route/property/ComponentParameterField.tsx b/karavan-designer/src/designer/route/property/ComponentParameterField.tsx index 23f8228a..d8adbd08 100644 --- a/karavan-designer/src/designer/route/property/ComponentParameterField.tsx +++ b/karavan-designer/src/designer/route/property/ComponentParameterField.tsx @@ -37,7 +37,7 @@ import "@patternfly/patternfly/patternfly.css"; import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon"; import {ComponentProperty} from "karavan-core/lib/model/ComponentModels"; import {CamelUi, RouteToCreate} from "../../utils/CamelUi"; -import {CamelElement, Integration} from "karavan-core/lib/model/IntegrationDefinition"; +import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition"; import {ToDefinition} from "karavan-core/lib/model/CamelDefinition"; import CompressIcon from "@patternfly/react-icons/dist/js/icons/compress-icon"; import ExpandIcon from "@patternfly/react-icons/dist/js/icons/expand-icon"; @@ -49,7 +49,7 @@ import ShowIcon from "@patternfly/react-icons/dist/js/icons/eye-icon"; import HideIcon from "@patternfly/react-icons/dist/js/icons/eye-slash-icon"; import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon"; import {usePropertiesHook} from "../usePropertiesHook"; -import {useDesignerStore, useIntegrationStore} from "../../DesignerStore"; +import {useIntegrationStore} from "../../DesignerStore"; import {shallow} from "zustand/shallow"; const prefix = "parameters"; diff --git a/karavan-designer/src/designer/route/property/DslPropertyField.tsx b/karavan-designer/src/designer/route/property/DslPropertyField.tsx index b07e8beb..70651b21 100644 --- a/karavan-designer/src/designer/route/property/DslPropertyField.tsx +++ b/karavan-designer/src/designer/route/property/DslPropertyField.tsx @@ -88,7 +88,7 @@ export function DslPropertyField(props: Props) { const [integration] = useIntegrationStore((state) => [state.integration], shallow) const [dark] = useDesignerStore((s) => [s.dark], shallow) - const [isShowAdvanced, setIsShowAdvanced] = useState<Map<string, boolean>>(new Map<string, boolean>()); + const [isShowAdvanced, setIsShowAdvanced] = useState<string[]>([]); const [arrayValues, setArrayValues] = useState<Map<string, string>>(new Map<string, string>()); const [selectStatus, setSelectStatus] = useState<Map<string, boolean>>(new Map<string, boolean>()); const [showEditor, setShowEditor] = useState<boolean>(false); @@ -654,19 +654,23 @@ export function DslPropertyField(props: Props) { </div> ) } - function getExpandableComponentParameters(properties: ComponentProperty[], label: string) { const element = props.element; + return ( <ExpandableSection toggleText={label} onToggle={(_event, isExpanded) => { setIsShowAdvanced(prevState => { - prevState.set(label, !prevState.get(label)); + if (isExpanded && !isShowAdvanced.includes(label)) { + prevState = [...prevState, label] + } else { + prevState = prevState.filter(s => s!== label); + } return prevState; }) }} - isExpanded={isShowAdvanced.has(label) && isShowAdvanced.get(label)}> + isExpanded={isShowAdvanced.includes(label)}> <div className="parameters"> {properties.map(kp => <ComponentParameterField diff --git a/karavan-space/src/designer/kamelet/KameletAnnotationsPanel.tsx b/karavan-space/src/designer/kamelet/KameletAnnotationsPanel.tsx index 076aec40..b1a6b324 100644 --- a/karavan-space/src/designer/kamelet/KameletAnnotationsPanel.tsx +++ b/karavan-space/src/designer/kamelet/KameletAnnotationsPanel.tsx @@ -75,8 +75,7 @@ export function KameletAnnotationsPanel() { return ( <GridItem span={span}> <FormGroup label={label} fieldId={key} isRequired> - {/* eslint-disable-next-line react/jsx-no-undef */} - <ToggleGroup aria-label={key}> + <ToggleGroup aria-label={key} id={key} name={key}> {values.map(value => <ToggleGroupItem key={value} diff --git a/karavan-space/src/designer/kamelet/KameletDefinitionPropertyCard.tsx b/karavan-space/src/designer/kamelet/KameletDefinitionPropertyCard.tsx index d8933df4..cf42ef6c 100644 --- a/karavan-space/src/designer/kamelet/KameletDefinitionPropertyCard.tsx +++ b/karavan-space/src/designer/kamelet/KameletDefinitionPropertyCard.tsx @@ -19,10 +19,18 @@ import { Button, Card, CardBody, - CardTitle, Flex, FlexItem, - FormGroup, FormSelect, FormSelectOption, + CardTitle, + Flex, + FlexItem, + FormGroup, + FormSelect, + FormSelectOption, Grid, - GridItem, Label, Modal, Switch, + GridItem, + Label, + LabelGroup, + Modal, + Switch, TextInput, } from '@patternfly/react-core'; import '../karavan.css'; @@ -30,6 +38,8 @@ import './kamelet.css'; import {useIntegrationStore} from "../DesignerStore"; import {shallow} from "zustand/shallow"; import {DefinitionProperty} from "karavan-core/lib/model/IntegrationDefinition"; +import {CamelUtil} from "karavan-core/lib/api/CamelUtil"; +import AddIcon from "@patternfly/react-icons/dist/js/icons/plus-circle-icon"; interface Props { index: number @@ -45,7 +55,7 @@ export function KameletDefinitionPropertyCard(props: Props) { const key = props.propKey; const required = integration.spec.definition?.required || []; - function setPropertyValue(field: string, value: string) { + function setPropertyValue(field: string, value: any) { if (integration.spec.definition?.properties) { (integration.spec.definition?.properties as any)[key][field] = value; setIntegration(integration, true); @@ -83,8 +93,9 @@ export function KameletDefinitionPropertyCard(props: Props) { aria-label="FormSelect Input" ouiaId="BasicFormSelect" > - {['string', 'number', 'boolean'].map((option, index) => ( - <FormSelectOption key={option} isDisabled={false} id={key + field} name={key + field} value={option} label={option} /> + {['string', 'number', 'integer', 'boolean'].map((option, index) => ( + <FormSelectOption key={option} isDisabled={false} id={key + field} name={key + field} + value={option} label={option}/> ))} </FormSelect> </FormGroup> @@ -92,9 +103,106 @@ export function KameletDefinitionPropertyCard(props: Props) { ) } + function sortEnum(source: string, dest: string) { + const i = CamelUtil.cloneIntegration(integration); + if (i.spec.definition && integration.spec.definition?.properties[key]) { + const enums: string [] = i.spec.definition.properties[key].enum; + console.log(enums) + if (enums && Array.isArray(enums)) { + console.log("isArray") + const from = enums.findIndex(e => source); + const to = enums.findIndex(e => dest); + if (from > -1 && to > -1) { + console.log("exchange"); + [enums[from], enums[to]] = [enums[to], enums[from]]; + i.spec.definition.properties[key].enum = enums; + console.log("i.spec.definition.properties[key].enum", i.spec.definition.properties[key].enum); + setIntegration(i, true); + } + } + } + } + + function addEnum() { + const i = CamelUtil.cloneIntegration(integration); + if (i.spec.definition && integration.spec.definition?.properties[key]) { + let enums: string [] = i.spec.definition.properties[key].enum; + if (enums && Array.isArray(enums)) { + enums.push("enum") + } else { + enums = ['enum']; + } + i.spec.definition.properties[key].enum = enums; + setIntegration(i, true); + } + } + + function deleteEnum(val: string) { + const enumVal = getPropertyValue('enum'); + const i = CamelUtil.cloneIntegration(integration); + if (enumVal && Array.isArray(enumVal) && i.spec.definition) { + const enums: string[] = [...enumVal]; + setPropertyValue('enum', enums.filter(e => e !== val)); + } + } + + function renameEnum(index: number, newVal: string) { + const enumVal = getPropertyValue('enum'); + const i = CamelUtil.cloneIntegration(integration); + if (enumVal && Array.isArray(enumVal) && i.spec.definition) { + const enums: string[] = [...enumVal]; + enums[index] = newVal; + setPropertyValue('enum', enums); + } + } + + function getPropertyEnumField(field: string, label: string, isRequired: boolean, span: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12) { + const enumVal = getPropertyValue(field); + return ( + <GridItem span={span}> + <FormGroup fieldId={key + field} isRequired={isRequired}> + <LabelGroup + categoryName={label} + numLabels={enumVal?.length || 0} + isEditable + addLabelControl={ + <Button variant="link" icon={<AddIcon/>} onClick={event => addEnum()}> + Add + </Button> + } + > + {enumVal && enumVal.map((val: string, index: number) => ( + <Label + key={val} + id={val} + color="grey" + isEditable + onClose={() => deleteEnum(val)} + onEditCancel={(_event, prevText) => {}} + onEditComplete={(event, newText) => { + if (event.type === 'mousedown') { + renameEnum(index, val) + } else if (event.type === 'keydown' && (event as KeyboardEvent).key === 'Tab') { + renameEnum(index, newText) + } else if (event.type === 'keydown' && (event as KeyboardEvent).key === 'Enter') { + renameEnum(index, newText) + } else { + renameEnum(index, val) + } + }} + > + {val} + </Label> + ))} + </LabelGroup> + </FormGroup> + </GridItem> + ) + } + function renameProperty(newKey: string) { const oldKey = key; - newKey = newKey.replace(/[\W_]+/g,''); + newKey = newKey.replace(/[\W_]+/g, ''); if (oldKey !== newKey) { if (integration.spec.definition?.properties) { const o = (integration.spec.definition?.properties as any) @@ -138,7 +246,6 @@ export function KameletDefinitionPropertyCard(props: Props) { } function setRequired(checked: boolean) { - console.log(required, key) const newRequired = [...required]; if (checked && !newRequired.includes(key)) { newRequired.push(key); @@ -146,7 +253,6 @@ export function KameletDefinitionPropertyCard(props: Props) { const index = newRequired.findIndex(r => r === key); newRequired.splice(index, 1); } - // console.log(newRequired) if (integration.spec.definition?.required) { integration.spec.definition.required.length = 0; integration.spec.definition.required.push(...newRequired) @@ -212,6 +318,7 @@ export function KameletDefinitionPropertyCard(props: Props) { {getPropertyField("format", "Format", false, 3)} {getPropertyField("example", "Example", false, 6)} {getPropertyField("default", "Default", false, 3)} + {getPropertyValue('type') === 'string' && getPropertyEnumField("enum", "Enum", true, 12)} {/*{getPropertyField("x-descriptors", "Descriptors", false, 12)}*/} </Grid> </CardBody> diff --git a/karavan-space/src/designer/kamelet/KameletDefinitionsPanel.tsx b/karavan-space/src/designer/kamelet/KameletDefinitionsPanel.tsx index 009a54ac..0d3f04a4 100644 --- a/karavan-space/src/designer/kamelet/KameletDefinitionsPanel.tsx +++ b/karavan-space/src/designer/kamelet/KameletDefinitionsPanel.tsx @@ -19,11 +19,13 @@ import { Button, Card, CardBody, - CardTitle, Flex, FlexItem, + CardTitle, + Flex, + FlexItem, Form, FormGroup, Grid, - GridItem, + GridItem, TextArea, TextInput, } from '@patternfly/react-core'; import '../karavan.css'; @@ -32,6 +34,9 @@ import {useIntegrationStore} from "../DesignerStore"; import {shallow} from "zustand/shallow"; import AddIcon from "@patternfly/react-icons/dist/js/icons/plus-circle-icon"; import {KameletDefinitionPropertyCard} from "./KameletDefinitionPropertyCard"; +import {CamelUtil} from "karavan-core/lib/api/CamelUtil"; +import {DefinitionProperty} from "karavan-core/lib/model/IntegrationDefinition"; +import {KameletDependenciesCard} from "./KameletDependenciesCard"; export function KameletDefinitionsPanel() { @@ -53,7 +58,7 @@ export function KameletDefinitionsPanel() { } } - function getElement(key: string, label: string, span: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12) { + function getElementTextInput(key: string, label: string, span: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12) { return ( <GridItem span={span}> <FormGroup label={label} fieldId={key} isRequired> @@ -65,7 +70,43 @@ export function KameletDefinitionsPanel() { ) } + function getElementTextArea(key: string, label: string, span: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12) { + return ( + <GridItem span={span}> + <FormGroup label={label} fieldId={key} isRequired> + <TextArea type="text" id={key} name={key} autoResize + onChange={(_, value) => setValue(key, value)} + value={getValue(key)}/> + </FormGroup> + </GridItem> + ) + } + const properties = integration.spec.definition?.properties ? Object.keys(integration.spec.definition?.properties) : []; + + function addNewProperty() { + const i = CamelUtil.cloneIntegration(integration); + if (i.spec.definition && integration.spec.definition?.properties) { + const propertyName = generatePropertyName(); + i.spec.definition.properties = Object.assign({[propertyName]: new DefinitionProperty()}, integration.spec.definition.properties); + setIntegration(i, true); + } + } + + function generatePropertyName(count: number = 0): string { + const prefix = 'property'; + const propName = 'property' + count; + if (integration.spec.definition?.properties) { + const keys = Object.keys(integration.spec.definition?.properties); + if (keys.includes(propName)) { + return generatePropertyName(count + 1); + } else { + return propName; + } + } + return prefix; + } + return ( <> <Card isCompact ouiaId="DefinitionsCard"> @@ -73,9 +114,9 @@ export function KameletDefinitionsPanel() { <CardBody> <Form> <Grid hasGutter> - {getElement('title', 'Title', 4)} - {getElement('description', 'Description', 6)} - {getElement('type', 'Type', 2)} + {getElementTextInput('title', 'Title', 3)} + {getElementTextArea('description', 'Description', 9)} + {/*{getElementTextInput('type', 'Type', 2)}*/} </Grid> </Form> </CardBody> @@ -86,7 +127,9 @@ export function KameletDefinitionsPanel() { <Flex> <FlexItem>Properties</FlexItem> <FlexItem align={{default: "alignRight"}}> - <Button variant={"link"} icon={<AddIcon/>}>Add property</Button> + <Button variant={"link"} icon={<AddIcon/>} onClick={event => addNewProperty()}> + Add property + </Button> </FlexItem> </Flex> </CardTitle> @@ -102,6 +145,8 @@ export function KameletDefinitionsPanel() { </Form> </CardBody> </Card> + <div style={{height: "20px"}}/> + <KameletDependenciesCard/> </> ) diff --git a/karavan-space/src/designer/kamelet/KameletDependenciesCard.tsx b/karavan-space/src/designer/kamelet/KameletDependenciesCard.tsx new file mode 100644 index 00000000..893c897b --- /dev/null +++ b/karavan-space/src/designer/kamelet/KameletDependenciesCard.tsx @@ -0,0 +1,113 @@ +/* + * 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, + Card, + CardBody, + CardTitle, + FormGroup, FormHelperText, HelperText, HelperTextItem, + Label, + LabelGroup, +} from '@patternfly/react-core'; +import '../karavan.css'; +import './kamelet.css'; +import {useIntegrationStore} from "../DesignerStore"; +import {shallow} from "zustand/shallow"; +import AddIcon from "@patternfly/react-icons/dist/js/icons/plus-circle-icon"; +import {CamelUtil} from "karavan-core/lib/api/CamelUtil"; + +export function KameletDependenciesCard() { + + const [integration, setIntegration] = useIntegrationStore((s) => [s.integration, s.setIntegration], shallow) + + const dependencies: string[] = [...(integration.spec.dependencies || [])]; + + + function setDependencies(deps: string[]) { + const i = CamelUtil.cloneIntegration(integration); + i.spec.dependencies = deps; + setIntegration(i, true); + } + + function addDepencency() { + dependencies.push("dependency") + setDependencies(dependencies); + } + + function deleteDependency(val: string) { + setDependencies(dependencies.filter(e => e !== val)); + } + + function renameDependency(index: number, newVal: string) { + dependencies[index] = newVal; + setDependencies(dependencies); + } + + return ( + <Card isClickable isCompact isFlat ouiaId="PropertyCard" className="property-card"> + <CardTitle> + Dependencies + </CardTitle> + <CardBody> + <FormHelperText> + <HelperText> + <HelperTextItem>Dependencies required, ex: camel:component or mvn:groupId:artifactId:version</HelperTextItem> + </HelperText> + </FormHelperText> + </CardBody> + <CardBody> + <FormGroup fieldId={'dependencies'}> + <LabelGroup + // categoryName={"Dependencies"} + numLabels={dependencies.length} + isEditable + addLabelControl={ + <Button variant="link" icon={<AddIcon/>} onClick={event => addDepencency()}> + Add + </Button> + } + > + {dependencies.map((val: string, index: number) => ( + <Label + key={val} + id={val} + color="grey" + isEditable + onClose={() => deleteDependency(val)} + onEditCancel={(_event, prevText) => {}} + onEditComplete={(event, newText) => { + if (event.type === 'mousedown') { + renameDependency(index, val) + } else if (event.type === 'keydown' && (event as KeyboardEvent).key === 'Tab') { + renameDependency(index, newText) + } else if (event.type === 'keydown' && (event as KeyboardEvent).key === 'Enter') { + renameDependency(index, newText) + } else { + renameDependency(index, val) + } + }} + > + {val} + </Label> + ))} + </LabelGroup> + </FormGroup> + </CardBody> + </Card> + ) +} diff --git a/karavan-space/src/designer/kamelet/KameletProperties.tsx b/karavan-space/src/designer/kamelet/KameletProperties.tsx index fc8f188f..e5a8cfc5 100644 --- a/karavan-space/src/designer/kamelet/KameletProperties.tsx +++ b/karavan-space/src/designer/kamelet/KameletProperties.tsx @@ -14,11 +14,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, {useEffect, useState} from 'react'; +import React from 'react'; import { Form, - FormGroup, - TextInput, Button, Title, Tooltip, Popover, InputGroup, InputGroupItem, } from '@patternfly/react-core'; import '../karavan.css'; import "@patternfly/patternfly/patternfly.css"; @@ -26,21 +24,6 @@ import { RegistryBeanDefinition, } from "karavan-core/lib/model/CamelDefinition"; import {Integration} from "karavan-core/lib/model/IntegrationDefinition"; -import {CamelUtil} from "karavan-core/lib/api/CamelUtil"; -import {SensitiveKeys} from "karavan-core/lib/model/CamelMetadata"; -import {v4 as uuidv4} from "uuid"; -import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon"; -import AddIcon from "@patternfly/react-icons/dist/js/icons/plus-circle-icon"; -import CloneIcon from '@patternfly/react-icons/dist/esm/icons/clone-icon' -import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon"; -import {InfrastructureSelector} from "../route/property/InfrastructureSelector"; -import KubernetesIcon from "@patternfly/react-icons/dist/js/icons/openshift-icon"; -import {InfrastructureAPI} from "../utils/InfrastructureAPI"; -import ShowIcon from "@patternfly/react-icons/dist/js/icons/eye-icon"; -import HideIcon from "@patternfly/react-icons/dist/js/icons/eye-slash-icon"; -import DockerIcon from "@patternfly/react-icons/dist/js/icons/docker-icon"; -import {useDesignerStore} from "../DesignerStore"; -import {shallow} from "zustand/shallow"; import {IntegrationHeader} from "../utils/IntegrationHeader"; @@ -53,197 +36,11 @@ interface Props { export function KameletProperties (props: Props) { - const [selectedStep] = useDesignerStore((s) => [s.selectedStep], shallow); - const [infrastructureSelector, setInfrastructureSelector] = useState<boolean>(false); - const [infrastructureSelectorProperty, setInfrastructureSelectorProperty] = useState<string | undefined>(undefined); - const [infrastructureSelectorUuid, setInfrastructureSelectorUuid] = useState<string | undefined>(undefined); - const [properties, setProperties] = useState<Map<string, [string, string, boolean]>>(new Map<string, [string, string, boolean]>()); - - useEffect(()=> { - setProperties(preparePropertiesMap((selectedStep as RegistryBeanDefinition)?.properties)) - }, [selectedStep?.uuid]) - - function preparePropertiesMap (properties: any): Map<string, [string, string, boolean]> { - const result = new Map<string, [string, string, boolean]>(); - if (properties) { - Object.keys(properties).forEach((k, i, a) => result.set(uuidv4(), [k, properties[k], false])); - } - return result; - } - - function onBeanPropertyUpdate () { - if (selectedStep) { - const bean = CamelUtil.cloneBean(selectedStep); - const beanProperties: any = {}; - properties.forEach((p: any) => beanProperties[p[0]] = p[1]); - bean.properties = beanProperties; - props.onChange(bean); - } - } - - function beanFieldChanged (fieldId: string, value: string) { - if (selectedStep) { - const bean = CamelUtil.cloneBean(selectedStep); - (bean as any)[fieldId] = value; - props.onChange(bean); - } - } - - function propertyChanged (uuid: string, key: string, value: string, showPassword: boolean) { - setProperties(prevState => { - prevState.set(uuid, [key, value, showPassword]); - return prevState; - }); - onBeanPropertyUpdate(); - } - - function propertyDeleted (uuid: string) { - setProperties(prevState => { - prevState.delete(uuid); - return prevState; - }) - onBeanPropertyUpdate(); - } - - function selectInfrastructure (value: string) { - const propertyId = infrastructureSelectorProperty; - const uuid = infrastructureSelectorUuid; - if (propertyId && uuid){ - if (value.startsWith("config") || value.startsWith("secret")) value = "{{" + value + "}}"; - propertyChanged(uuid, propertyId, value, false); - setInfrastructureSelector(false); - setInfrastructureSelectorProperty(undefined); - } - } - - function openInfrastructureSelector (uuid: string, propertyName: string) { - setInfrastructureSelector(true); - setInfrastructureSelectorProperty(propertyName); - setInfrastructureSelectorUuid(uuid); - } - - function closeInfrastructureSelector () { - setInfrastructureSelector(false); - } - - function getInfrastructureSelectorModal() { - return ( - <InfrastructureSelector - dark={false} - isOpen={infrastructureSelector} - onClose={() => closeInfrastructureSelector()} - onSelect={selectInfrastructure}/>) - } - - function cloneBean () { - if (selectedStep) { - const bean = CamelUtil.cloneBean(selectedStep); - bean.uuid = uuidv4(); - props.onClone(bean); - } - } - - function getLabelIcon (displayName: string, description: string) { - return ( - <Popover - position={"left"} - headerContent={displayName} - bodyContent={description} - footerContent={ - <div> - <b>Required</b> - </div> - }> - <button type="button" aria-label="More info" onClick={e => { - e.preventDefault(); - e.stopPropagation(); - }} className="pf-v5-c-form__group-label-help"> - <HelpIcon /> - </button> - </Popover> - ) - } - function getBeanForm() { - const bean = (selectedStep as RegistryBeanDefinition); - return ( - <> - <div className="headers"> - <div className="top"> - <Title headingLevel="h1" size="md">Bean</Title> - <Tooltip content="Clone bean" position="bottom"> - <Button variant="link" onClick={() => cloneBean()} icon={<CloneIcon/>}/> - </Tooltip> - </div> - </div> - <FormGroup label="Name" fieldId="name" isRequired labelIcon={getLabelIcon("Name", "Bean name used as a reference ex: myBean")}> - <TextInput className="text-field" isRequired type="text" id="name" name="name" value={bean?.name} - onChange={(_, value)=> beanFieldChanged("name", value)}/> - </FormGroup> - <FormGroup label="Type" fieldId="type" isRequired labelIcon={getLabelIcon("Type", "Bean class Canonical Name ex: org.demo.MyBean")}> - <TextInput className="text-field" isRequired type="text" id="type" name="type" value={bean?.type} - onChange={(_, value) => beanFieldChanged("type", value)}/> - </FormGroup> - <FormGroup label="Properties" fieldId="properties" className="bean-properties"> - {Array.from(properties.entries()).map((v, index, array) => { - const i = v[0]; - const key = v[1][0]; - const value = v[1][1]; - const showPassword = v[1][2]; - const isSecret = key !== undefined && SensitiveKeys.includes(key.toLowerCase()); - const inInfrastructure = InfrastructureAPI.infrastructure !== 'local'; - const icon = InfrastructureAPI.infrastructure === 'kubernetes' ? <KubernetesIcon/> : <DockerIcon/> - return ( - <div key={"key-" + i} className="bean-property"> - <TextInput placeholder="Bean Field Name" className="text-field" isRequired type="text" id={"key-" + i} - name={"key-" + i} value={key} - onChange={(_, beanFieldName) => { - propertyChanged(i, beanFieldName, value, showPassword) - }}/> - <InputGroup> - {inInfrastructure && - <Tooltip position="bottom-end" content="Select value from Infrastructure"> - <Button variant="control" onClick={e => openInfrastructureSelector(i, key)}> - {icon} - </Button> - </Tooltip>} - <InputGroupItem isFill> - <TextInput - placeholder="Bean Field Value" - type={isSecret && !showPassword ? "password" : "text"} - className="text-field" - isRequired - id={"value-" + i} - name={"value-" + i} - value={value} - onChange={(_, value) => { - propertyChanged(i, key, value, showPassword) - }}/> - </InputGroupItem> - {isSecret && <Tooltip position="bottom-end" content={showPassword ? "Hide" : "Show"}> - <Button variant="control" onClick={e => propertyChanged(i, key, value, !showPassword)}> - {showPassword ? <ShowIcon/> : <HideIcon/>} - </Button> - </Tooltip>} - </InputGroup> - <Button variant="link" className="delete-button" onClick={e => propertyDeleted(i)}><DeleteIcon/></Button> - </div> - ) - })} - <Button variant="link" className="add-button" onClick={e => propertyChanged(uuidv4(), '', '', false)}> - <AddIcon/>Add property</Button> - </FormGroup> - </> - ) - } - - const bean = (selectedStep as RegistryBeanDefinition); return ( - <div className='properties' key={bean ? bean.uuid : 'integration'}> + <div className='properties' key={'integration'}> <Form autoComplete="off" onSubmit={event => event.preventDefault()}> - {bean === undefined && <IntegrationHeader/>} - {bean !== undefined && getBeanForm()} + <IntegrationHeader/> </Form> - {getInfrastructureSelectorModal()} </div> ) } diff --git a/karavan-space/src/designer/kamelet/KameletTypesOutCard.tsx b/karavan-space/src/designer/kamelet/KameletTypesOutCard.tsx new file mode 100644 index 00000000..76b590ee --- /dev/null +++ b/karavan-space/src/designer/kamelet/KameletTypesOutCard.tsx @@ -0,0 +1,113 @@ +/* + * 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, + Card, + CardBody, + CardTitle, + FormGroup, FormHelperText, HelperText, HelperTextItem, + Label, + LabelGroup, +} from '@patternfly/react-core'; +import '../karavan.css'; +import './kamelet.css'; +import {useIntegrationStore} from "../DesignerStore"; +import {shallow} from "zustand/shallow"; +import AddIcon from "@patternfly/react-icons/dist/js/icons/plus-circle-icon"; +import {CamelUtil} from "karavan-core/lib/api/CamelUtil"; + +export function KameletTypesOutCard() { + + const [integration, setIntegration] = useIntegrationStore((s) => [s.integration, s.setIntegration], shallow) + + const dependencies: string[] = [...(integration.spec.dependencies || [])]; + + + function setDependencies(deps: string[]) { + const i = CamelUtil.cloneIntegration(integration); + i.spec.dependencies = deps; + setIntegration(i, true); + } + + function addDepencency() { + dependencies.push("dependency") + setDependencies(dependencies); + } + + function deleteDependency(val: string) { + setDependencies(dependencies.filter(e => e !== val)); + } + + function renameDependency(index: number, newVal: string) { + dependencies[index] = newVal; + setDependencies(dependencies); + } + + return ( + <Card isClickable isCompact isFlat ouiaId="PropertyCard" className="property-card"> + <CardTitle> + Dependencies + </CardTitle> + <CardBody> + <FormHelperText> + <HelperText> + <HelperTextItem>Dependencies required, ex: camel:component or mvn:groupId:artifactId:version</HelperTextItem> + </HelperText> + </FormHelperText> + </CardBody> + <CardBody> + <FormGroup fieldId={'dependencies'}> + <LabelGroup + // categoryName={"Dependencies"} + numLabels={dependencies.length} + isEditable + addLabelControl={ + <Button variant="link" icon={<AddIcon/>} onClick={event => addDepencency()}> + Add + </Button> + } + > + {dependencies.map((val: string, index: number) => ( + <Label + key={val} + id={val} + color="grey" + isEditable + onClose={() => deleteDependency(val)} + onEditCancel={(_event, prevText) => {}} + onEditComplete={(event, newText) => { + if (event.type === 'mousedown') { + renameDependency(index, val) + } else if (event.type === 'keydown' && (event as KeyboardEvent).key === 'Tab') { + renameDependency(index, newText) + } else if (event.type === 'keydown' && (event as KeyboardEvent).key === 'Enter') { + renameDependency(index, newText) + } else { + renameDependency(index, val) + } + }} + > + {val} + </Label> + ))} + </LabelGroup> + </FormGroup> + </CardBody> + </Card> + ) +} diff --git a/karavan-space/src/designer/kamelet/kamelet.css b/karavan-space/src/designer/kamelet/kamelet.css index bbdf28a9..c72acad2 100644 --- a/karavan-space/src/designer/kamelet/kamelet.css +++ b/karavan-space/src/designer/kamelet/kamelet.css @@ -23,6 +23,7 @@ padding-bottom: 106px; } +.karavan .kamelet-designer .pf-v5-c-drawer__content, .karavan .kamelet-designer .main { background-color: var(--pf-v5-global--BackgroundColor--light-300); } diff --git a/karavan-space/src/designer/karavan.css b/karavan-space/src/designer/karavan.css index fb04c267..31aa866b 100644 --- a/karavan-space/src/designer/karavan.css +++ b/karavan-space/src/designer/karavan.css @@ -343,6 +343,7 @@ flex-direction: column; justify-content: space-between; margin-bottom: 20px; + padding-bottom: 30px; } .karavan .pf-v5-c-drawer__splitter { diff --git a/karavan-space/src/designer/route/property/ComponentParameterField.tsx b/karavan-space/src/designer/route/property/ComponentParameterField.tsx index 23f8228a..d8adbd08 100644 --- a/karavan-space/src/designer/route/property/ComponentParameterField.tsx +++ b/karavan-space/src/designer/route/property/ComponentParameterField.tsx @@ -37,7 +37,7 @@ import "@patternfly/patternfly/patternfly.css"; import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon"; import {ComponentProperty} from "karavan-core/lib/model/ComponentModels"; import {CamelUi, RouteToCreate} from "../../utils/CamelUi"; -import {CamelElement, Integration} from "karavan-core/lib/model/IntegrationDefinition"; +import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition"; import {ToDefinition} from "karavan-core/lib/model/CamelDefinition"; import CompressIcon from "@patternfly/react-icons/dist/js/icons/compress-icon"; import ExpandIcon from "@patternfly/react-icons/dist/js/icons/expand-icon"; @@ -49,7 +49,7 @@ import ShowIcon from "@patternfly/react-icons/dist/js/icons/eye-icon"; import HideIcon from "@patternfly/react-icons/dist/js/icons/eye-slash-icon"; import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon"; import {usePropertiesHook} from "../usePropertiesHook"; -import {useDesignerStore, useIntegrationStore} from "../../DesignerStore"; +import {useIntegrationStore} from "../../DesignerStore"; import {shallow} from "zustand/shallow"; const prefix = "parameters"; diff --git a/karavan-space/src/designer/route/property/DslPropertyField.tsx b/karavan-space/src/designer/route/property/DslPropertyField.tsx index b07e8beb..70651b21 100644 --- a/karavan-space/src/designer/route/property/DslPropertyField.tsx +++ b/karavan-space/src/designer/route/property/DslPropertyField.tsx @@ -88,7 +88,7 @@ export function DslPropertyField(props: Props) { const [integration] = useIntegrationStore((state) => [state.integration], shallow) const [dark] = useDesignerStore((s) => [s.dark], shallow) - const [isShowAdvanced, setIsShowAdvanced] = useState<Map<string, boolean>>(new Map<string, boolean>()); + const [isShowAdvanced, setIsShowAdvanced] = useState<string[]>([]); const [arrayValues, setArrayValues] = useState<Map<string, string>>(new Map<string, string>()); const [selectStatus, setSelectStatus] = useState<Map<string, boolean>>(new Map<string, boolean>()); const [showEditor, setShowEditor] = useState<boolean>(false); @@ -654,19 +654,23 @@ export function DslPropertyField(props: Props) { </div> ) } - function getExpandableComponentParameters(properties: ComponentProperty[], label: string) { const element = props.element; + return ( <ExpandableSection toggleText={label} onToggle={(_event, isExpanded) => { setIsShowAdvanced(prevState => { - prevState.set(label, !prevState.get(label)); + if (isExpanded && !isShowAdvanced.includes(label)) { + prevState = [...prevState, label] + } else { + prevState = prevState.filter(s => s!== label); + } return prevState; }) }} - isExpanded={isShowAdvanced.has(label) && isShowAdvanced.get(label)}> + isExpanded={isShowAdvanced.includes(label)}> <div className="parameters"> {properties.map(kp => <ComponentParameterField diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/kamelet/KameletAnnotationsPanel.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/kamelet/KameletAnnotationsPanel.tsx index 076aec40..b1a6b324 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/kamelet/KameletAnnotationsPanel.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/designer/kamelet/KameletAnnotationsPanel.tsx @@ -75,8 +75,7 @@ export function KameletAnnotationsPanel() { return ( <GridItem span={span}> <FormGroup label={label} fieldId={key} isRequired> - {/* eslint-disable-next-line react/jsx-no-undef */} - <ToggleGroup aria-label={key}> + <ToggleGroup aria-label={key} id={key} name={key}> {values.map(value => <ToggleGroupItem key={value} diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/karavan.css b/karavan-web/karavan-app/src/main/webui/src/designer/karavan.css index fb04c267..31aa866b 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/karavan.css +++ b/karavan-web/karavan-app/src/main/webui/src/designer/karavan.css @@ -343,6 +343,7 @@ flex-direction: column; justify-content: space-between; margin-bottom: 20px; + padding-bottom: 30px; } .karavan .pf-v5-c-drawer__splitter { diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/property/ComponentParameterField.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/route/property/ComponentParameterField.tsx index 23f8228a..d8adbd08 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/route/property/ComponentParameterField.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/property/ComponentParameterField.tsx @@ -37,7 +37,7 @@ import "@patternfly/patternfly/patternfly.css"; import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon"; import {ComponentProperty} from "karavan-core/lib/model/ComponentModels"; import {CamelUi, RouteToCreate} from "../../utils/CamelUi"; -import {CamelElement, Integration} from "karavan-core/lib/model/IntegrationDefinition"; +import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition"; import {ToDefinition} from "karavan-core/lib/model/CamelDefinition"; import CompressIcon from "@patternfly/react-icons/dist/js/icons/compress-icon"; import ExpandIcon from "@patternfly/react-icons/dist/js/icons/expand-icon"; @@ -49,7 +49,7 @@ import ShowIcon from "@patternfly/react-icons/dist/js/icons/eye-icon"; import HideIcon from "@patternfly/react-icons/dist/js/icons/eye-slash-icon"; import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon"; import {usePropertiesHook} from "../usePropertiesHook"; -import {useDesignerStore, useIntegrationStore} from "../../DesignerStore"; +import {useIntegrationStore} from "../../DesignerStore"; import {shallow} from "zustand/shallow"; const prefix = "parameters"; diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/property/DslPropertyField.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/route/property/DslPropertyField.tsx index b07e8beb..70651b21 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/route/property/DslPropertyField.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/property/DslPropertyField.tsx @@ -88,7 +88,7 @@ export function DslPropertyField(props: Props) { const [integration] = useIntegrationStore((state) => [state.integration], shallow) const [dark] = useDesignerStore((s) => [s.dark], shallow) - const [isShowAdvanced, setIsShowAdvanced] = useState<Map<string, boolean>>(new Map<string, boolean>()); + const [isShowAdvanced, setIsShowAdvanced] = useState<string[]>([]); const [arrayValues, setArrayValues] = useState<Map<string, string>>(new Map<string, string>()); const [selectStatus, setSelectStatus] = useState<Map<string, boolean>>(new Map<string, boolean>()); const [showEditor, setShowEditor] = useState<boolean>(false); @@ -654,19 +654,23 @@ export function DslPropertyField(props: Props) { </div> ) } - function getExpandableComponentParameters(properties: ComponentProperty[], label: string) { const element = props.element; + return ( <ExpandableSection toggleText={label} onToggle={(_event, isExpanded) => { setIsShowAdvanced(prevState => { - prevState.set(label, !prevState.get(label)); + if (isExpanded && !isShowAdvanced.includes(label)) { + prevState = [...prevState, label] + } else { + prevState = prevState.filter(s => s!== label); + } return prevState; }) }} - isExpanded={isShowAdvanced.has(label) && isShowAdvanced.get(label)}> + isExpanded={isShowAdvanced.includes(label)}> <div className="parameters"> {properties.map(kp => <ComponentParameterField