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 7efc3af456fa0ecb65584a990b969a5e2b5f9363 Author: Marat Gubaidullin <ma...@talismancloud.io> AuthorDate: Wed Jan 31 16:57:44 2024 -0500 #1091 in App --- .../src/main/webui/src/designer/DesignerStore.ts | 16 ++++- .../main/webui/src/designer/KaravanDesigner.tsx | 8 ++- .../webui/src/designer/property/DslProperties.css | 7 +- .../webui/src/designer/property/DslProperties.tsx | 10 ++- .../property/property/ComponentPropertyField.tsx | 49 +++++++++---- .../ComponentPropertyPlaceholderDropdown.css | 24 +++++++ .../ComponentPropertyPlaceholderDropdown.tsx | 81 ++++++++++++++++++++++ .../property/property/DslPropertyField.tsx | 1 + .../src/main/webui/src/project/FileEditor.tsx | 18 ++++- .../src/main/webui/src/util/StringUtils.ts | 21 ++++++ 10 files changed, 205 insertions(+), 30 deletions(-) diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/DesignerStore.ts b/karavan-web/karavan-app/src/main/webui/src/designer/DesignerStore.ts index 407713a7..fe198a7f 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/DesignerStore.ts +++ b/karavan-web/karavan-app/src/main/webui/src/designer/DesignerStore.ts @@ -168,7 +168,8 @@ type DesignerState = { height: number, top: number, left: number, - moveElements: [string | undefined, string | undefined] + moveElements: [string | undefined, string | undefined], + propertyPlaceholders: string[]; } const designerState: DesignerState = { @@ -186,7 +187,8 @@ const designerState: DesignerState = { height: 0, top: 0, left: 0, - moveElements: [undefined, undefined] + moveElements: [undefined, undefined], + propertyPlaceholders: [] }; type DesignerAction = { @@ -203,6 +205,7 @@ type DesignerAction = { reset: () => void; setNotification: (notificationBadge: boolean, notificationMessage: [string, string]) => void; setMoveElements: (moveElements: [string | undefined, string | undefined]) => void; + setPropertyPlaceholders: (propertyPlaceholders: string[]) => void; } export const useDesignerStore = createWithEqualityFn<DesignerState & DesignerAction>((set) => ({ @@ -257,5 +260,12 @@ export const useDesignerStore = createWithEqualityFn<DesignerState & DesignerAct }, setMoveElements: (moveElements: [string | undefined, string | undefined]) => { set({moveElements: moveElements}) - } + }, + setPropertyPlaceholders: (propertyPlaceholders: string[]) => { + set((state: DesignerState) => { + state.propertyPlaceholders.length = 0; + state.propertyPlaceholders.push(...propertyPlaceholders); + return state; + }) + }, }), shallow) \ No newline at end of file diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx index 626f984b..349ae548 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx @@ -41,7 +41,6 @@ import {BeansDesigner} from "./beans/BeansDesigner"; import {CodeEditor} from "./editor/CodeEditor"; import BellIcon from '@patternfly/react-icons/dist/esm/icons/bell-icon'; import {KameletDesigner} from "./kamelet/KameletDesigner"; -import {v4 as uuidv4} from "uuid"; interface Props { onSave: (filename: string, yaml: string, propertyOnly: boolean) => void @@ -53,13 +52,15 @@ interface Props { hideLogDSL?: boolean showCodeTab: boolean tab?: "routes" | "rest" | "beans" + propertyPlaceholders: string[] } export function KaravanDesigner(props: Props) { const [tab, setTab] = useState<string>('routes'); - const [setDark, hideLogDSL, setHideLogDSL, setSelectedStep, reset, badge, message] = useDesignerStore((s) => - [s.setDark, s.hideLogDSL, s.setHideLogDSL, s.setSelectedStep, s.reset, s.notificationBadge, s.notificationMessage], shallow) + const [setDark, hideLogDSL, setHideLogDSL, setSelectedStep, reset, badge, message, setPropertyPlaceholders] = + useDesignerStore((s) => + [s.setDark, s.hideLogDSL, s.setHideLogDSL, s.setSelectedStep, s.reset, s.notificationBadge, s.notificationMessage, s.setPropertyPlaceholders], shallow) const [integration, setIntegration] = useIntegrationStore((s) => [s.integration, s.setIntegration], shallow) @@ -83,6 +84,7 @@ export function KaravanDesigner(props: Props) { setTab(designerTab || 'routes') reset(); setDark(props.dark); + setPropertyPlaceholders(props.propertyPlaceholders) setHideLogDSL(props.hideLogDSL === true); return () => { sub?.unsubscribe(); diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/property/DslProperties.css b/karavan-web/karavan-app/src/main/webui/src/designer/property/DslProperties.css index dd7cb199..43562bcb 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/property/DslProperties.css +++ b/karavan-web/karavan-app/src/main/webui/src/designer/property/DslProperties.css @@ -248,10 +248,15 @@ min-height: 6px; } +.karavan .properties .header-menu-toggle { + padding-left: 6px; + padding-right: 0; +} + .karavan .properties .component-headers { margin-left: 24px; } .karavan .properties .component-headers .pf-v5-c-clipboard-copy.pf-m-inline { background-color: transparent; -} \ No newline at end of file +} diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/property/DslProperties.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/property/DslProperties.tsx index 6169d656..ed56d2eb 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/property/DslProperties.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/designer/property/DslProperties.tsx @@ -27,7 +27,7 @@ import { MenuToggleElement, MenuToggle, DropdownList, - DropdownItem, Label, Flex, LabelGroup, Popover, FlexItem, Badge, ClipboardCopy, ClipboardCopyAction, + DropdownItem, Flex, Popover, FlexItem, Badge, ClipboardCopy, } from '@patternfly/react-core'; import '../karavan.css'; import './DslProperties.css'; @@ -85,18 +85,16 @@ export function DslProperties(props: Props) { const showMenu = hasSteps || targetDsl !== undefined; return showMenu ? <Dropdown - style={{inset: "0px auto auto -70px important!"}} - className={"xxx"} + popperProps={{position: "end"}} isOpen={isMenuOpen} onSelect={() => { }} onOpenChange={(isOpen: boolean) => setMenuOpen(isOpen)} toggle={(toggleRef: React.Ref<MenuToggleElement>) => ( <MenuToggle - style={{width: "240px", display: "flex", flexDirection: "row", justifyContent: "end"}} - className={"zzzz"} + className="header-menu-toggle" ref={toggleRef} - aria-label="kebab dropdown toggle" + aria-label="menu" variant="plain" onClick={() => setMenuOpen(!isMenuOpen)} isExpanded={isMenuOpen} diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/property/property/ComponentPropertyField.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/property/property/ComponentPropertyField.tsx index ae63e323..a25acafc 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/property/property/ComponentPropertyField.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/designer/property/property/ComponentPropertyField.tsx @@ -21,10 +21,13 @@ import { Popover, Switch, InputGroup, - TextArea, Tooltip, Button, - capitalize, InputGroupItem, TextInputGroup, TextVariants, Text + capitalize, + InputGroupItem, + TextInputGroup, + TextVariants, + Text, } from '@patternfly/react-core'; import { Select, @@ -51,6 +54,7 @@ import {shallow} from "zustand/shallow"; import {KubernetesIcon} from "../../icons/ComponentIcons"; import EditorIcon from "@patternfly/react-icons/dist/js/icons/code-icon"; import {ModalEditor} from "./ModalEditor"; +import {ComponentPropertyPlaceholderDropdown} from "./ComponentPropertyPlaceholderDropdown"; const prefix = "parameters"; const beanPrefix = "#bean:"; @@ -67,16 +71,18 @@ export function ComponentPropertyField(props: Props) { const {onParametersChange, getInternalComponentName} = usePropertiesHook(); const [integration] = useIntegrationStore((state) => [state.integration], shallow) - const [dark, setSelectedStep] = useDesignerStore((s) => [s.dark, s.setSelectedStep], shallow) + const [dark, setSelectedStep, propertyPlaceholders] = useDesignerStore((s) => + [s.dark, s.setSelectedStep, s.propertyPlaceholders], shallow) const [selectStatus, setSelectStatus] = useState<Map<string, boolean>>(new Map<string, boolean>()); const [showEditor, setShowEditor] = useState<boolean>(false); const [showPassword, setShowPassword] = useState<boolean>(false); const [infrastructureSelector, setInfrastructureSelector] = useState<boolean>(false); const [infrastructureSelectorProperty, setInfrastructureSelectorProperty] = useState<string | undefined>(undefined); - const [id, setId] = useState<string>(prefix + "-" + props.property.name); + const ref = useRef<any>(null); + const [isOpenPlaceholdersDropdown, setOpenPlaceholdersDropdown] = useState<boolean>(false); function parametersChanged(parameter: string, value: string | number | boolean | any, pathParameter?: boolean, newRoute?: RouteToCreate) { @@ -262,20 +268,30 @@ export function ComponentPropertyField(props: Props) { </Button> </Tooltip> } + <InputGroupItem> + <ComponentPropertyPlaceholderDropdown property={property}/> + </InputGroupItem> </InputGroup> } - function getTextInput(property: ComponentProperty, value: any) { + function getSpecialStringInput(property: ComponentProperty, value: any) { return ( - <TextInput - className="text-field" isRequired - type={(property.secret ? "password" : "text")} - id={id} name={id} - value={value !== undefined ? value : property.defaultValue} - customIcon={<Text component={TextVariants.p}>{property.type}</Text>} - onChange={(_, value) => { - parametersChanged(property.name, value, property.kind === 'path') - }}/> + <InputGroup> + <InputGroupItem isFill> + <TextInput + className="text-field" isRequired + type={(property.secret ? "password" : "text")} + id={id} name={id} + value={value !== undefined ? value : property.defaultValue} + customIcon={<Text component={TextVariants.p}>{property.type}</Text>} + onChange={(_, value) => { + parametersChanged(property.name, value, property.kind === 'path') + }}/> + </InputGroupItem> + <InputGroupItem> + <ComponentPropertyPlaceholderDropdown property={property}/> + </InputGroupItem> + </InputGroup> ) } @@ -330,6 +346,9 @@ export function ComponentPropertyField(props: Props) { onChange={(_, v) => parametersChanged(property.name, v)} /> </InputGroupItem> + <InputGroupItem> + <ComponentPropertyPlaceholderDropdown property={property}/> + </InputGroupItem> </TextInputGroup> ) } @@ -362,7 +381,7 @@ export function ComponentPropertyField(props: Props) { {property.type === 'string' && property.enum === undefined && !canBeInternalUri(property) && getStringInput(property, value)} {['duration', 'integer', 'int', 'number'].includes(property.type) && property.enum === undefined && !canBeInternalUri(property) - && getTextInput(property, value)} + && getSpecialStringInput(property, value)} {['object'].includes(property.type) && !property.enum && getSelectBean(property, value)} {['string', 'object'].includes(property.type) && property.enum diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/property/property/ComponentPropertyPlaceholderDropdown.css b/karavan-web/karavan-app/src/main/webui/src/designer/property/property/ComponentPropertyPlaceholderDropdown.css new file mode 100644 index 00000000..7d5d2d43 --- /dev/null +++ b/karavan-web/karavan-app/src/main/webui/src/designer/property/property/ComponentPropertyPlaceholderDropdown.css @@ -0,0 +1,24 @@ +/* + * 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. + */ + +.karavan .properties .property-placeholder-toggle { + padding-left: 6px; + padding-right: 6px; +} +.karavan .properties .property-placeholder-toggle .pf-v5-c-menu-toggle__controls { + display: none; +} \ No newline at end of file diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/property/property/ComponentPropertyPlaceholderDropdown.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/property/property/ComponentPropertyPlaceholderDropdown.tsx new file mode 100644 index 00000000..270f9549 --- /dev/null +++ b/karavan-web/karavan-app/src/main/webui/src/designer/property/property/ComponentPropertyPlaceholderDropdown.tsx @@ -0,0 +1,81 @@ +/* + * 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, {useState} from 'react'; +import { + Dropdown, + MenuToggleElement, + MenuToggle, + DropdownList, DropdownItem +} from '@patternfly/react-core'; +import '../../karavan.css'; +import './ComponentPropertyPlaceholderDropdown.css'; +import "@patternfly/patternfly/patternfly.css"; +import {ComponentProperty} from "karavan-core/lib/model/ComponentModels"; +import {RouteToCreate} from "../../utils/CamelUi"; +import {usePropertiesHook} from "../usePropertiesHook"; +import {useDesignerStore} from "../../DesignerStore"; +import {shallow} from "zustand/shallow"; +import EllipsisVIcon from "@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon"; + + +interface Props { + property: ComponentProperty, +} + +export function ComponentPropertyPlaceholderDropdown(props: Props) { + + const {onParametersChange} = usePropertiesHook(); + const [propertyPlaceholders] = useDesignerStore((s) => [s.propertyPlaceholders], shallow) + const [isOpenPlaceholdersDropdown, setOpenPlaceholdersDropdown] = useState<boolean>(false); + + function parametersChanged(parameter: string, value: string | number | boolean | any, pathParameter?: boolean, newRoute?: RouteToCreate) { + onParametersChange(parameter, value, pathParameter, newRoute); + } + + const property: ComponentProperty = props.property; + return ( + propertyPlaceholders && propertyPlaceholders.length > 0 ? + <Dropdown + popperProps={{position: "end"}} + isOpen={isOpenPlaceholdersDropdown} + onSelect={(_, value) => { + parametersChanged(property.name, `{{${value}}}`, property.kind === 'path') + setOpenPlaceholdersDropdown(false); + }} + onOpenChange={(isOpen: boolean) => setOpenPlaceholdersDropdown(isOpen)} + toggle={(toggleRef: React.Ref<MenuToggleElement>) => ( + <MenuToggle className="property-placeholder-toggle" + ref={toggleRef} + aria-label="placeholder menu" + variant="default" + onClick={() => setOpenPlaceholdersDropdown(!isOpenPlaceholdersDropdown)} + isExpanded={isOpenPlaceholdersDropdown} + > + <EllipsisVIcon/> + </MenuToggle> + )} + shouldFocusToggleOnSelect + > + <DropdownList> + {propertyPlaceholders.map((pp, index) => + <DropdownItem value={pp} key={index}>{pp}</DropdownItem> + )} + </DropdownList> + </Dropdown> + : <></> + ) +} diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/property/property/DslPropertyField.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/property/property/DslPropertyField.tsx index aa093618..59066667 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/property/property/DslPropertyField.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/designer/property/property/DslPropertyField.tsx @@ -74,6 +74,7 @@ import { import {TemplateApi} from "karavan-core/lib/api/TemplateApi"; import {KubernetesIcon} from "../../icons/ComponentIcons"; import {BeanProperties} from "./BeanProperties"; +import {ComponentPropertyPlaceholderDropdown} from "./ComponentPropertyPlaceholderDropdown"; interface Props { property: PropertyMeta, diff --git a/karavan-web/karavan-app/src/main/webui/src/project/FileEditor.tsx b/karavan-web/karavan-app/src/main/webui/src/project/FileEditor.tsx index 7193243a..6fe00b39 100644 --- a/karavan-web/karavan-app/src/main/webui/src/project/FileEditor.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/project/FileEditor.tsx @@ -14,15 +14,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; +import React, {useEffect, useState} from 'react'; import '../designer/karavan.css'; import Editor from "@monaco-editor/react"; import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml"; -import {ProjectFile} from "../api/ProjectModels"; +import {Project, ProjectFile} from "../api/ProjectModels"; import {useFilesStore, useFileStore} from "../api/ProjectStore"; import {KaravanDesigner} from "../designer/KaravanDesigner"; import {ProjectService} from "../api/ProjectService"; import {shallow} from "zustand/shallow"; +import {KaravanApi} from "../api/KaravanApi"; +import {getPropertyPlaceholders} from "../util/StringUtils"; interface Props { projectId: string @@ -37,6 +39,17 @@ const languages = new Map<string, string>([ export function FileEditor (props: Props) { const [file, designerTab] = useFileStore((s) => [s.file, s.designerTab], shallow ) + const [files] = useFilesStore((s) => [s.files], shallow); + const [propertyPlaceholders, setPropertyPlaceholders] = useState<string[]>([]); + + useEffect(() => { + const pp = getPropertyPlaceholders(files); + setPropertyPlaceholders(prevState => { + prevState.length = 0; + prevState.push(...pp); + return prevState; + }) + }, []); function save (name: string, code: string) { if (file) { @@ -63,6 +76,7 @@ export function FileEditor (props: Props) { onSaveCustomCode={(name, code) => ProjectService.updateFile(new ProjectFile(name + ".java", props.projectId, code, Date.now()), false)} onGetCustomCode={onGetCustomCode} + propertyPlaceholders={propertyPlaceholders} /> ) } diff --git a/karavan-web/karavan-app/src/main/webui/src/util/StringUtils.ts b/karavan-web/karavan-app/src/main/webui/src/util/StringUtils.ts index 407778f1..6b8a8986 100644 --- a/karavan-web/karavan-app/src/main/webui/src/util/StringUtils.ts +++ b/karavan-web/karavan-app/src/main/webui/src/util/StringUtils.ts @@ -1,3 +1,24 @@ +import {ProjectFile} from "../api/ProjectModels"; + export function isEmpty(str: string) { return !str?.trim(); +} + +export function getPropertyPlaceholders(files: ProjectFile[]): string[] { + const result: string[] = [] + const file = files.filter(f => f.name === 'application.properties')?.at(0); + if (file) { + const code = file.code; + const lines = code.split('\n').map((line) => line.trim()); + lines + .filter(line => !line.startsWith("camel.") && !line.startsWith("jkube.") && !line.startsWith("jib.")) + .filter(line => line !== undefined && line !== null && line.length > 0) + .forEach(line => { + const parts = line.split("="); + if (parts.length > 0) { + result.push(parts[0]); + } + }) + } + return result; } \ No newline at end of file