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 b2736628 Fix #1093
b2736628 is described below

commit b2736628f11e45b4ceeef74ed6272c2d32331151
Author: Marat Gubaidullin <ma...@talismancloud.io>
AuthorDate: Thu Feb 1 15:51:06 2024 -0500

    Fix #1093
---
 karavan-designer/src/DesignerPage.tsx              |  5 +-
 karavan-designer/src/designer/KaravanDesigner.tsx  |  2 +
 .../property/property/ComponentPropertyField.tsx   |  6 +-
 .../ComponentPropertyPlaceholderDropdown.css       | 13 +++
 .../ComponentPropertyPlaceholderDropdown.tsx       | 96 ++++++++++++++++++----
 .../src/designer/route/RouteDesigner.tsx           |  8 +-
 .../src/designer/utils/InfrastructureAPI.ts        |  5 ++
 karavan-vscode/src/designerView.ts                 |  3 +
 karavan-vscode/src/utils.ts                        |  9 ++
 karavan-vscode/webview/App.tsx                     |  5 ++
 .../main/webui/src/designer/KaravanDesigner.tsx    |  2 +
 .../property/property/ComponentPropertyField.tsx   |  6 +-
 .../ComponentPropertyPlaceholderDropdown.css       | 13 +++
 .../ComponentPropertyPlaceholderDropdown.tsx       | 96 ++++++++++++++++++----
 .../webui/src/designer/route/RouteDesigner.tsx     |  8 +-
 .../webui/src/designer/utils/InfrastructureAPI.ts  |  5 ++
 .../src/main/webui/src/project/FileEditor.tsx      | 13 ++-
 .../src/main/webui/src/util/StringUtils.ts         | 10 ++-
 18 files changed, 246 insertions(+), 59 deletions(-)

diff --git a/karavan-designer/src/DesignerPage.tsx 
b/karavan-designer/src/DesignerPage.tsx
index 125e3626..16e4efd8 100644
--- a/karavan-designer/src/DesignerPage.tsx
+++ b/karavan-designer/src/DesignerPage.tsx
@@ -78,9 +78,10 @@ export const DesignerPage = (props: Props) => {
                     console.log(name1, code)
                 }}
                 propertyPlaceholders={[
-                    "timer.delay",
-                    "sql.query"
+                    // "timer.delay",
+                    // "sql.query"
                 ]}
+                onSavePropertyPlaceholder={(key, value) => 
console.log("onSavePropertyPlaceholder", key, value)}
             />
         )
     }
diff --git a/karavan-designer/src/designer/KaravanDesigner.tsx 
b/karavan-designer/src/designer/KaravanDesigner.tsx
index 349ae548..7f201e4c 100644
--- a/karavan-designer/src/designer/KaravanDesigner.tsx
+++ b/karavan-designer/src/designer/KaravanDesigner.tsx
@@ -46,6 +46,7 @@ interface Props {
     onSave: (filename: string, yaml: string, propertyOnly: boolean) => void
     onSaveCustomCode: (name: string, code: string) => void
     onGetCustomCode: (name: string, javaType: string) => Promise<string | 
undefined>
+    onSavePropertyPlaceholder: (key: string, value: string) => void
     filename: string
     yaml: string
     dark: boolean
@@ -70,6 +71,7 @@ export function KaravanDesigner(props: Props) {
         InfrastructureAPI.setOnSaveCustomCode(props.onSaveCustomCode);
         InfrastructureAPI.setOnGetCustomCode(props.onGetCustomCode);
         InfrastructureAPI.setOnSave(props.onSave);
+        
InfrastructureAPI.setOnSavePropertyPlaceholder(props.onSavePropertyPlaceholder);
 
         setSelectedStep(undefined);
         const i = makeIntegration(props.yaml, props.filename);
diff --git 
a/karavan-designer/src/designer/property/property/ComponentPropertyField.tsx 
b/karavan-designer/src/designer/property/property/ComponentPropertyField.tsx
index a25acafc..402aaf8b 100644
--- a/karavan-designer/src/designer/property/property/ComponentPropertyField.tsx
+++ b/karavan-designer/src/designer/property/property/ComponentPropertyField.tsx
@@ -269,7 +269,7 @@ export function ComponentPropertyField(props: Props) {
                 </Tooltip>
             }
             <InputGroupItem>
-                <ComponentPropertyPlaceholderDropdown property={property}/>
+                <ComponentPropertyPlaceholderDropdown property={property} 
value={value}/>
             </InputGroupItem>
         </InputGroup>
     }
@@ -289,7 +289,7 @@ export function ComponentPropertyField(props: Props) {
                         }}/>
                 </InputGroupItem>
                 <InputGroupItem>
-                    <ComponentPropertyPlaceholderDropdown property={property}/>
+                    <ComponentPropertyPlaceholderDropdown property={property} 
value={value}/>
                 </InputGroupItem>
             </InputGroup>
         )
@@ -347,7 +347,7 @@ export function ComponentPropertyField(props: Props) {
                     />
                 </InputGroupItem>
                 <InputGroupItem>
-                    <ComponentPropertyPlaceholderDropdown property={property}/>
+                    <ComponentPropertyPlaceholderDropdown property={property} 
value={value}/>
                 </InputGroupItem>
             </TextInputGroup>
         )
diff --git 
a/karavan-designer/src/designer/property/property/ComponentPropertyPlaceholderDropdown.css
 
b/karavan-designer/src/designer/property/property/ComponentPropertyPlaceholderDropdown.css
index 7d5d2d43..43991634 100644
--- 
a/karavan-designer/src/designer/property/property/ComponentPropertyPlaceholderDropdown.css
+++ 
b/karavan-designer/src/designer/property/property/ComponentPropertyPlaceholderDropdown.css
@@ -19,6 +19,19 @@
     padding-left: 6px;
     padding-right: 6px;
 }
+
+.karavan .properties .property-placeholder-toggle 
.pf-v5-c-button__icon.pf-m-start {
+    margin-inline-end: 0;
+}
+
 .karavan .properties .property-placeholder-toggle 
.pf-v5-c-menu-toggle__controls {
     display: none;
+}
+
+.pf-v5-c-popover .property-placeholder-toggle-form {
+    width: 300px;
+}
+
+.pf-v5-c-popover .property-placeholder-toggle-form .pf-v5-c-form__group {
+    grid-template-columns: 1fr 2fr;
 }
\ No newline at end of file
diff --git 
a/karavan-designer/src/designer/property/property/ComponentPropertyPlaceholderDropdown.tsx
 
b/karavan-designer/src/designer/property/property/ComponentPropertyPlaceholderDropdown.tsx
index 270f9549..136f90fb 100644
--- 
a/karavan-designer/src/designer/property/property/ComponentPropertyPlaceholderDropdown.tsx
+++ 
b/karavan-designer/src/designer/property/property/ComponentPropertyPlaceholderDropdown.tsx
@@ -19,7 +19,7 @@ import {
     Dropdown,
     MenuToggleElement,
     MenuToggle,
-    DropdownList, DropdownItem
+    DropdownList, DropdownItem, Popover, Badge, TextVariants, Text, Flex, 
TextInput, FormGroup, Form, Button, FlexItem
 } from '@patternfly/react-core';
 import '../../karavan.css';
 import './ComponentPropertyPlaceholderDropdown.css';
@@ -30,25 +30,99 @@ 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";
-
+import AddIcon from "@patternfly/react-icons/dist/js/icons/plus-icon";
+import {InfrastructureAPI} from "../../utils/InfrastructureAPI";
 
 interface Props {
     property: ComponentProperty,
+    value: any
 }
 
 export function ComponentPropertyPlaceholderDropdown(props: Props) {
 
     const {onParametersChange} = usePropertiesHook();
-    const [propertyPlaceholders] = useDesignerStore((s) => 
[s.propertyPlaceholders], shallow)
+    const [propertyPlaceholders, setPropertyPlaceholders] = 
useDesignerStore((s) =>
+        [s.propertyPlaceholders, s.setPropertyPlaceholders], shallow)
     const [isOpenPlaceholdersDropdown, setOpenPlaceholdersDropdown] = 
useState<boolean>(false);
+    const [propValue, setPropValue] = useState<string>('');
+    const [isVisible, setIsVisible] = React.useState(false);
+
+    const {property, value} = props;
+    const valueIsPlaceholder: boolean = value && 
value.toString().startsWith('{{') && value.toString().endsWith('}}');
+    const placeholderValue = valueIsPlaceholder ? 
value.toString().replace('{{', '').replace('}}', '') : undefined;
+    const showAddButton = valueIsPlaceholder && 
!propertyPlaceholders.includes(placeholderValue);
+    const popoverId = "popover-selector-" + property.name;
 
     function parametersChanged(parameter: string, value: string | number | 
boolean | any, pathParameter?: boolean, newRoute?: RouteToCreate) {
         onParametersChange(parameter, value, pathParameter, newRoute);
     }
 
-    const property: ComponentProperty = props.property;
+    function onMenuToggleClick() {
+        if (!showAddButton) {
+            setOpenPlaceholdersDropdown(!isOpenPlaceholdersDropdown)
+        }
+    }
+
+    function saveProperty() {
+        InfrastructureAPI.onSavePropertyPlaceholder(placeholderValue, 
propValue);
+        setIsVisible(false);
+        const p = [...propertyPlaceholders]
+        p.push(placeholderValue);
+        setPropertyPlaceholders(p);
+    }
+
+    function getPopover() {
+        return (
+            <Popover
+                isVisible={isVisible}
+                shouldOpen={(_event, _fn) => setIsVisible(true)}
+                shouldClose={(_event, _fn) => setIsVisible(false)}
+                aria-label="Add property"
+                headerContent={"Add property"}
+                bodyContent={
+                    <Form isHorizontal 
className="property-placeholder-toggle-form" autoComplete="off">
+                        <FormGroup isInline label="Property" isRequired 
fieldId="property">
+                            <TextInput id="property" readOnly 
value={placeholderValue}/>
+                        </FormGroup>
+                        <FormGroup isInline label="Value" isRequired 
fieldId="value">
+                            <TextInput id="value" isRequired value={propValue}
+                                       onChange={(_, value) => 
setPropValue(value)}/>
+                        </FormGroup>
+                    </Form>
+                }
+                footerContent={
+                    <Flex>
+                        <FlexItem align={{default: "alignRight"}}>
+                            <Button
+                                onClick={() => saveProperty()}>
+                                Save
+                            </Button>
+                        </FlexItem>
+                    </Flex>
+                }
+                triggerRef={() => document.getElementById(popoverId) as 
HTMLButtonElement}
+            />
+        )
+    }
+
+    function getToggle(toggleRef: React.Ref<MenuToggleElement>) {
+        return (
+            <MenuToggle className="property-placeholder-toggle"
+                        id={popoverId}
+                        ref={toggleRef}
+                        aria-label="placeholder menu"
+                        variant="default"
+                        onClick={() => onMenuToggleClick()}
+                        isExpanded={isOpenPlaceholdersDropdown}
+            >
+                {showAddButton ? <AddIcon/> : <EllipsisVIcon/>}
+                {showAddButton && getPopover()}
+            </MenuToggle>
+        )
+    }
+
     return (
-        propertyPlaceholders && propertyPlaceholders.length > 0 ?
+        (propertyPlaceholders && propertyPlaceholders.length > 0 ) || 
showAddButton ?
             <Dropdown
                 popperProps={{position: "end"}}
                 isOpen={isOpenPlaceholdersDropdown}
@@ -57,17 +131,7 @@ export function ComponentPropertyPlaceholderDropdown(props: 
Props) {
                     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>
-                )}
+                toggle={(toggleRef: React.Ref<MenuToggleElement>) => 
getToggle(toggleRef)}
                 shouldFocusToggleOnSelect
             >
                 <DropdownList>
diff --git a/karavan-designer/src/designer/route/RouteDesigner.tsx 
b/karavan-designer/src/designer/route/RouteDesigner.tsx
index c2ebf292..7a36f814 100644
--- a/karavan-designer/src/designer/route/RouteDesigner.tsx
+++ b/karavan-designer/src/designer/route/RouteDesigner.tsx
@@ -40,7 +40,7 @@ import {DslElementMoveModal} from 
"./element/DslElementMoveModal";
 
 export function RouteDesigner() {
 
-    const {openSelector, createRouteConfiguration, onCommand, handleKeyDown, 
handleKeyUp, unselectElement, onDslSelect,
+    const {openSelector, createRouteConfiguration, onCommand, unselectElement, 
onDslSelect,
         isSourceKamelet, isActionKamelet, isKamelet, isSinkKamelet} = 
useRouteDesignerHook();
 
     const [integration] = useIntegrationStore((state) => [state.integration], 
shallow)
@@ -72,12 +72,9 @@ export function RouteDesigner() {
     const flowRef = useRef<HTMLDivElement | null>(null);
 
     useEffect(()=> {
-        // window.addEventListener('resize', changeGraphSize);
         const interval = setInterval(() => {
             changeGraphSize();
         }, 500);
-        window.addEventListener('keydown', handleKeyDown);
-        window.addEventListener('keyup', handleKeyUp);
         const commandSub = EventBus.onCommand()?.subscribe((command: Command) 
=> onCommand(command, printerRef));
         if (flowRef.current === null) {
             clearSteps();
@@ -86,9 +83,6 @@ export function RouteDesigner() {
         }
         return ()=> {
             clearInterval(interval)
-            // window.removeEventListener('resize', changeGraphSize);
-            window.removeEventListener('keydown', handleKeyDown);
-            window.removeEventListener('keyup', handleKeyUp);
             commandSub?.unsubscribe();
         }
     }, [showSelector, integration])
diff --git a/karavan-designer/src/designer/utils/InfrastructureAPI.ts 
b/karavan-designer/src/designer/utils/InfrastructureAPI.ts
index f0d8faef..c2d5ca7c 100644
--- a/karavan-designer/src/designer/utils/InfrastructureAPI.ts
+++ b/karavan-designer/src/designer/utils/InfrastructureAPI.ts
@@ -20,6 +20,7 @@ export class InfrastructureAPI {
     static onGetCustomCode: (name: string, javaType: string) => Promise<string 
| undefined>;
     static onSaveCustomCode: (name: string, code: string) => void;
     static onSave: (filename: string, yaml: string, propertyOnly: boolean) => 
void;
+    static onSavePropertyPlaceholder: (key: string, value: string) => void;
 
     static setOnGetCustomCode(onGetCustomCode: (name: string, javaType: 
string) => Promise<string | undefined>){
         this.onGetCustomCode = onGetCustomCode
@@ -33,6 +34,10 @@ export class InfrastructureAPI {
         this.onSave = onSave
     }
 
+    static setOnSavePropertyPlaceholder(onSavePropertyPlaceholder:(key: 
string, value: string) => void){
+        this.onSavePropertyPlaceholder = onSavePropertyPlaceholder
+    }
+
     // Kubernetes/Docker API
     static infrastructure: 'kubernetes' | 'docker' | 'local' = 'local';
     static configMaps: string[] = [];
diff --git a/karavan-vscode/src/designerView.ts 
b/karavan-vscode/src/designerView.ts
index 1f6879de..2b4da788 100644
--- a/karavan-vscode/src/designerView.ts
+++ b/karavan-vscode/src/designerView.ts
@@ -133,6 +133,9 @@ export class DesignerView {
                         case 'saveCode':
                             utils.saveCode(message.name, message.yamlFullPath, 
message.yamFileName, message.code);
                             break;
+                        case 'savePropertyPlaceholder':
+                            utils.savePropertyPlaceholder(message.key, 
message.value);
+                            break;    
                         case 'getData':
                             this.sendData(panel, filename, relativePath, 
fullPath, message.reread === true, yaml, tab);
                             break;
diff --git a/karavan-vscode/src/utils.ts b/karavan-vscode/src/utils.ts
index 6cb7083b..8462cbc4 100644
--- a/karavan-vscode/src/utils.ts
+++ b/karavan-vscode/src/utils.ts
@@ -38,6 +38,15 @@ export function saveCode(name: string, yamlFullPath: string, 
yamFileName: string
     }
 }
 
+export async function savePropertyPlaceholder(key: string, value: string) {
+    if (workspace.workspaceFolders) {
+        const uriFolder: Uri = workspace.workspaceFolders[0].uri;
+        const properties = await getProperties();
+        const text = 
properties.concat('\n').concat(key).concat('=').concat(value);
+        write(path.join(uriFolder.path, "application.properties"), text);
+    }
+}
+
 export function deleteFile(fullPath: string) {
     if (workspace.workspaceFolders) {
         const uriFile: Uri = Uri.file(path.resolve(fullPath));
diff --git a/karavan-vscode/webview/App.tsx b/karavan-vscode/webview/App.tsx
index e539d7f1..7611f8fb 100644
--- a/karavan-vscode/webview/App.tsx
+++ b/karavan-vscode/webview/App.tsx
@@ -189,6 +189,10 @@ class App extends React.Component<Props, State> {
     vscode.postMessage({ command: 'saveCode', name: name, yamlFullPath: 
this.state.fullPath, yamFileName: this.state.filename, code: code });
   }
 
+  savePropertyPlaceholder(key: string, value: string) {
+    vscode.postMessage({ command: 'savePropertyPlaceholder', key: key, value: 
value });
+  }
+
   saveIntegrationFiles(files: any) {
     const f = Object.keys(files).map(key => new IntegrationFile(key, 
files[key]));
     this.setState({ files: f });
@@ -223,6 +227,7 @@ class App extends React.Component<Props, State> {
               return new Promise<string | undefined>(resolve => resolve(code))
             }}
             propertyPlaceholders={this.state.propertyPlaceholders}
+            onSavePropertyPlaceholder={(key, value) => 
this.savePropertyPlaceholder(key, value)}
           />
         }
         {loaded && page === "knowledgebase" && <KnowledgebasePage dark={dark} 
/>}
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 349ae548..7f201e4c 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
@@ -46,6 +46,7 @@ interface Props {
     onSave: (filename: string, yaml: string, propertyOnly: boolean) => void
     onSaveCustomCode: (name: string, code: string) => void
     onGetCustomCode: (name: string, javaType: string) => Promise<string | 
undefined>
+    onSavePropertyPlaceholder: (key: string, value: string) => void
     filename: string
     yaml: string
     dark: boolean
@@ -70,6 +71,7 @@ export function KaravanDesigner(props: Props) {
         InfrastructureAPI.setOnSaveCustomCode(props.onSaveCustomCode);
         InfrastructureAPI.setOnGetCustomCode(props.onGetCustomCode);
         InfrastructureAPI.setOnSave(props.onSave);
+        
InfrastructureAPI.setOnSavePropertyPlaceholder(props.onSavePropertyPlaceholder);
 
         setSelectedStep(undefined);
         const i = makeIntegration(props.yaml, props.filename);
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 a25acafc..402aaf8b 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
@@ -269,7 +269,7 @@ export function ComponentPropertyField(props: Props) {
                 </Tooltip>
             }
             <InputGroupItem>
-                <ComponentPropertyPlaceholderDropdown property={property}/>
+                <ComponentPropertyPlaceholderDropdown property={property} 
value={value}/>
             </InputGroupItem>
         </InputGroup>
     }
@@ -289,7 +289,7 @@ export function ComponentPropertyField(props: Props) {
                         }}/>
                 </InputGroupItem>
                 <InputGroupItem>
-                    <ComponentPropertyPlaceholderDropdown property={property}/>
+                    <ComponentPropertyPlaceholderDropdown property={property} 
value={value}/>
                 </InputGroupItem>
             </InputGroup>
         )
@@ -347,7 +347,7 @@ export function ComponentPropertyField(props: Props) {
                     />
                 </InputGroupItem>
                 <InputGroupItem>
-                    <ComponentPropertyPlaceholderDropdown property={property}/>
+                    <ComponentPropertyPlaceholderDropdown property={property} 
value={value}/>
                 </InputGroupItem>
             </TextInputGroup>
         )
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
index 7d5d2d43..43991634 100644
--- 
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
@@ -19,6 +19,19 @@
     padding-left: 6px;
     padding-right: 6px;
 }
+
+.karavan .properties .property-placeholder-toggle 
.pf-v5-c-button__icon.pf-m-start {
+    margin-inline-end: 0;
+}
+
 .karavan .properties .property-placeholder-toggle 
.pf-v5-c-menu-toggle__controls {
     display: none;
+}
+
+.pf-v5-c-popover .property-placeholder-toggle-form {
+    width: 300px;
+}
+
+.pf-v5-c-popover .property-placeholder-toggle-form .pf-v5-c-form__group {
+    grid-template-columns: 1fr 2fr;
 }
\ 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
index 270f9549..136f90fb 100644
--- 
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
@@ -19,7 +19,7 @@ import {
     Dropdown,
     MenuToggleElement,
     MenuToggle,
-    DropdownList, DropdownItem
+    DropdownList, DropdownItem, Popover, Badge, TextVariants, Text, Flex, 
TextInput, FormGroup, Form, Button, FlexItem
 } from '@patternfly/react-core';
 import '../../karavan.css';
 import './ComponentPropertyPlaceholderDropdown.css';
@@ -30,25 +30,99 @@ 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";
-
+import AddIcon from "@patternfly/react-icons/dist/js/icons/plus-icon";
+import {InfrastructureAPI} from "../../utils/InfrastructureAPI";
 
 interface Props {
     property: ComponentProperty,
+    value: any
 }
 
 export function ComponentPropertyPlaceholderDropdown(props: Props) {
 
     const {onParametersChange} = usePropertiesHook();
-    const [propertyPlaceholders] = useDesignerStore((s) => 
[s.propertyPlaceholders], shallow)
+    const [propertyPlaceholders, setPropertyPlaceholders] = 
useDesignerStore((s) =>
+        [s.propertyPlaceholders, s.setPropertyPlaceholders], shallow)
     const [isOpenPlaceholdersDropdown, setOpenPlaceholdersDropdown] = 
useState<boolean>(false);
+    const [propValue, setPropValue] = useState<string>('');
+    const [isVisible, setIsVisible] = React.useState(false);
+
+    const {property, value} = props;
+    const valueIsPlaceholder: boolean = value && 
value.toString().startsWith('{{') && value.toString().endsWith('}}');
+    const placeholderValue = valueIsPlaceholder ? 
value.toString().replace('{{', '').replace('}}', '') : undefined;
+    const showAddButton = valueIsPlaceholder && 
!propertyPlaceholders.includes(placeholderValue);
+    const popoverId = "popover-selector-" + property.name;
 
     function parametersChanged(parameter: string, value: string | number | 
boolean | any, pathParameter?: boolean, newRoute?: RouteToCreate) {
         onParametersChange(parameter, value, pathParameter, newRoute);
     }
 
-    const property: ComponentProperty = props.property;
+    function onMenuToggleClick() {
+        if (!showAddButton) {
+            setOpenPlaceholdersDropdown(!isOpenPlaceholdersDropdown)
+        }
+    }
+
+    function saveProperty() {
+        InfrastructureAPI.onSavePropertyPlaceholder(placeholderValue, 
propValue);
+        setIsVisible(false);
+        const p = [...propertyPlaceholders]
+        p.push(placeholderValue);
+        setPropertyPlaceholders(p);
+    }
+
+    function getPopover() {
+        return (
+            <Popover
+                isVisible={isVisible}
+                shouldOpen={(_event, _fn) => setIsVisible(true)}
+                shouldClose={(_event, _fn) => setIsVisible(false)}
+                aria-label="Add property"
+                headerContent={"Add property"}
+                bodyContent={
+                    <Form isHorizontal 
className="property-placeholder-toggle-form" autoComplete="off">
+                        <FormGroup isInline label="Property" isRequired 
fieldId="property">
+                            <TextInput id="property" readOnly 
value={placeholderValue}/>
+                        </FormGroup>
+                        <FormGroup isInline label="Value" isRequired 
fieldId="value">
+                            <TextInput id="value" isRequired value={propValue}
+                                       onChange={(_, value) => 
setPropValue(value)}/>
+                        </FormGroup>
+                    </Form>
+                }
+                footerContent={
+                    <Flex>
+                        <FlexItem align={{default: "alignRight"}}>
+                            <Button
+                                onClick={() => saveProperty()}>
+                                Save
+                            </Button>
+                        </FlexItem>
+                    </Flex>
+                }
+                triggerRef={() => document.getElementById(popoverId) as 
HTMLButtonElement}
+            />
+        )
+    }
+
+    function getToggle(toggleRef: React.Ref<MenuToggleElement>) {
+        return (
+            <MenuToggle className="property-placeholder-toggle"
+                        id={popoverId}
+                        ref={toggleRef}
+                        aria-label="placeholder menu"
+                        variant="default"
+                        onClick={() => onMenuToggleClick()}
+                        isExpanded={isOpenPlaceholdersDropdown}
+            >
+                {showAddButton ? <AddIcon/> : <EllipsisVIcon/>}
+                {showAddButton && getPopover()}
+            </MenuToggle>
+        )
+    }
+
     return (
-        propertyPlaceholders && propertyPlaceholders.length > 0 ?
+        (propertyPlaceholders && propertyPlaceholders.length > 0 ) || 
showAddButton ?
             <Dropdown
                 popperProps={{position: "end"}}
                 isOpen={isOpenPlaceholdersDropdown}
@@ -57,17 +131,7 @@ export function ComponentPropertyPlaceholderDropdown(props: 
Props) {
                     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>
-                )}
+                toggle={(toggleRef: React.Ref<MenuToggleElement>) => 
getToggle(toggleRef)}
                 shouldFocusToggleOnSelect
             >
                 <DropdownList>
diff --git 
a/karavan-web/karavan-app/src/main/webui/src/designer/route/RouteDesigner.tsx 
b/karavan-web/karavan-app/src/main/webui/src/designer/route/RouteDesigner.tsx
index c2ebf292..7a36f814 100644
--- 
a/karavan-web/karavan-app/src/main/webui/src/designer/route/RouteDesigner.tsx
+++ 
b/karavan-web/karavan-app/src/main/webui/src/designer/route/RouteDesigner.tsx
@@ -40,7 +40,7 @@ import {DslElementMoveModal} from 
"./element/DslElementMoveModal";
 
 export function RouteDesigner() {
 
-    const {openSelector, createRouteConfiguration, onCommand, handleKeyDown, 
handleKeyUp, unselectElement, onDslSelect,
+    const {openSelector, createRouteConfiguration, onCommand, unselectElement, 
onDslSelect,
         isSourceKamelet, isActionKamelet, isKamelet, isSinkKamelet} = 
useRouteDesignerHook();
 
     const [integration] = useIntegrationStore((state) => [state.integration], 
shallow)
@@ -72,12 +72,9 @@ export function RouteDesigner() {
     const flowRef = useRef<HTMLDivElement | null>(null);
 
     useEffect(()=> {
-        // window.addEventListener('resize', changeGraphSize);
         const interval = setInterval(() => {
             changeGraphSize();
         }, 500);
-        window.addEventListener('keydown', handleKeyDown);
-        window.addEventListener('keyup', handleKeyUp);
         const commandSub = EventBus.onCommand()?.subscribe((command: Command) 
=> onCommand(command, printerRef));
         if (flowRef.current === null) {
             clearSteps();
@@ -86,9 +83,6 @@ export function RouteDesigner() {
         }
         return ()=> {
             clearInterval(interval)
-            // window.removeEventListener('resize', changeGraphSize);
-            window.removeEventListener('keydown', handleKeyDown);
-            window.removeEventListener('keyup', handleKeyUp);
             commandSub?.unsubscribe();
         }
     }, [showSelector, integration])
diff --git 
a/karavan-web/karavan-app/src/main/webui/src/designer/utils/InfrastructureAPI.ts
 
b/karavan-web/karavan-app/src/main/webui/src/designer/utils/InfrastructureAPI.ts
index f0d8faef..c2d5ca7c 100644
--- 
a/karavan-web/karavan-app/src/main/webui/src/designer/utils/InfrastructureAPI.ts
+++ 
b/karavan-web/karavan-app/src/main/webui/src/designer/utils/InfrastructureAPI.ts
@@ -20,6 +20,7 @@ export class InfrastructureAPI {
     static onGetCustomCode: (name: string, javaType: string) => Promise<string 
| undefined>;
     static onSaveCustomCode: (name: string, code: string) => void;
     static onSave: (filename: string, yaml: string, propertyOnly: boolean) => 
void;
+    static onSavePropertyPlaceholder: (key: string, value: string) => void;
 
     static setOnGetCustomCode(onGetCustomCode: (name: string, javaType: 
string) => Promise<string | undefined>){
         this.onGetCustomCode = onGetCustomCode
@@ -33,6 +34,10 @@ export class InfrastructureAPI {
         this.onSave = onSave
     }
 
+    static setOnSavePropertyPlaceholder(onSavePropertyPlaceholder:(key: 
string, value: string) => void){
+        this.onSavePropertyPlaceholder = onSavePropertyPlaceholder
+    }
+
     // Kubernetes/Docker API
     static infrastructure: 'kubernetes' | 'docker' | 'local' = 'local';
     static configMaps: string[] = [];
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 6fe00b39..e5bece48 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
@@ -24,7 +24,7 @@ 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";
+import {getPropertyCode, getPropertyPlaceholders} from "../util/StringUtils";
 
 interface Props {
     projectId: string
@@ -59,10 +59,18 @@ export function FileEditor (props: Props) {
     }
 
     function onGetCustomCode (name: string, javaType: string): Promise<string 
| undefined> {
-        const files = useFilesStore.getState().files;
         return new Promise<string | undefined>(resolve => 
resolve(files.filter(f => f.name === name + ".java")?.at(0)?.code));
     }
 
+    function onSavePropertyPlaceholder (key: string, value: string) {
+        const file = files.filter(f => f.name === 
'application.properties')?.at(0);
+        const code = 
file?.code?.concat('\n').concat(key).concat('=').concat(value);
+        if (file && code) {
+            file.code = code;
+            ProjectService.updateFile(file, false);
+        }
+    }
+
     function getDesigner () {
         return (
             file !== undefined &&
@@ -77,6 +85,7 @@ export function FileEditor (props: Props) {
                     ProjectService.updateFile(new ProjectFile(name + ".java", 
props.projectId, code, Date.now()), false)}
                 onGetCustomCode={onGetCustomCode}
                 propertyPlaceholders={propertyPlaceholders}
+                onSavePropertyPlaceholder={onSavePropertyPlaceholder}
             />
         )
     }
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 6b8a8986..026dc7a1 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
@@ -6,9 +6,8 @@ export function isEmpty(str: string) {
 
 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 code = getPropertyCode(files);
+    if (code) {
         const lines = code.split('\n').map((line) => line.trim());
         lines
             .filter(line => !line.startsWith("camel.") && 
!line.startsWith("jkube.") && !line.startsWith("jib."))
@@ -21,4 +20,9 @@ export function getPropertyPlaceholders(files: 
ProjectFile[]): string[] {
         })
     }
     return result;
+}
+
+export function getPropertyCode(files: ProjectFile[]) {
+    const file = files.filter(f => f.name === 'application.properties')?.at(0);
+    return file?.code;
 }
\ No newline at end of file


Reply via email to