You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ma...@apache.org on 2023/10/26 00:59:41 UTC

[camel-karavan] 01/02: Kamelet Editor in App for #315

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 40d3914dd74b219fbd17e3a07780714a1f0d3172
Author: Marat Gubaidullin <ma...@talismancloud.io>
AuthorDate: Wed Oct 25 20:42:16 2023 -0400

    Kamelet Editor in App for #315
---
 .../webui/src/knowledgebase/KnowledgebasePage.tsx  | 22 +----
 .../karavan-app/src/main/webui/src/main/Main.tsx   |  6 +-
 .../src/main/webui/src/project/ProjectPage.tsx     |  2 +-
 .../webui/src/project/files/CreateFileModal.tsx    | 99 ++++++++++++++++++----
 4 files changed, 91 insertions(+), 38 deletions(-)

diff --git a/karavan-web/karavan-app/src/main/webui/src/knowledgebase/KnowledgebasePage.tsx b/karavan-web/karavan-app/src/main/webui/src/knowledgebase/KnowledgebasePage.tsx
index 35368b54..02c6b641 100644
--- a/karavan-web/karavan-app/src/main/webui/src/knowledgebase/KnowledgebasePage.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/knowledgebase/KnowledgebasePage.tsx
@@ -16,21 +16,7 @@
  */
 import React, {useState} from 'react';
 import '../designer/karavan.css';
-import {
-    Button,
-    Flex,
-    FlexItem,
-    PageSection,
-    Switch,
-    Tab,
-    Tabs,
-    Text,
-    TextContent,
-    TextInput,
-    Toolbar,
-    ToolbarContent,
-    ToolbarItem
-} from "@patternfly/react-core";
+import {Flex, FlexItem, PageSection, Switch, Tab, Tabs, Text, TextContent, TextInput, Toolbar, ToolbarContent, ToolbarItem} from "@patternfly/react-core";
 import {MainToolbar} from "../designer/MainToolbar";
 import {KameletsTab} from "./kamelets/KameletsTab";
 import {EipTab} from "./eip/EipTab";
@@ -38,7 +24,6 @@ import {ComponentsTab} from "./components/ComponentsTab";
 
 interface Props {
     dark: boolean,
-    onKameletsReload? (): void;
 }
 
 export const KnowledgebasePage = (props: Props) => {
@@ -56,11 +41,6 @@ export const KnowledgebasePage = (props: Props) => {
     function getTools() {
         return <Toolbar id="toolbar-group-types">
             <ToolbarContent>
-                {tab === 'kamelets' && <ToolbarItem>
-                    <Button
-                        onClick={(_event) => props.onKameletsReload?.()}
-                    >Reload</Button>
-                </ToolbarItem>}
                 {tab === 'kamelets' && <ToolbarItem>
                     <Switch
                         label="Custom only"
diff --git a/karavan-web/karavan-app/src/main/webui/src/main/Main.tsx b/karavan-web/karavan-app/src/main/webui/src/main/Main.tsx
index a2a8d5c9..a7a1e457 100644
--- a/karavan-web/karavan-app/src/main/webui/src/main/Main.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/main/Main.tsx
@@ -16,7 +16,7 @@
  */
 
 import {Navigate, Route, Routes} from 'react-router-dom';
-import React, {useEffect, useRef} from "react";
+import React, {useEffect, useMemo, useRef} from "react";
 import {KaravanApi} from "../api/KaravanApi";
 import {
     Bullseye,
@@ -132,6 +132,10 @@ export function Main() {
         return !showStepper() && !showSpinner() && (KaravanApi.isAuthorized || KaravanApi.authType === 'public');
     }
 
+    const projectPage = useMemo(() =>
+            <Route path="/projects/:projectId" element={<ProjectPage key={'project'}/>}/>
+        , []);
+
     return (
         <Page className="karavan">
             {showSpinner() &&
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/ProjectPage.tsx b/karavan-web/karavan-app/src/main/webui/src/project/ProjectPage.tsx
index 1df4f70e..43feee58 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/ProjectPage.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/ProjectPage.tsx
@@ -65,7 +65,7 @@ export function ProjectPage() {
     }
 
     const buildIn = isBuildIn();
-
+    console.log("Project refresh")
     const showFilePanel = file !== undefined && operation === 'select';
     return (
         <PageSection className="project-page" padding={{default: 'noPadding'}}>
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/files/CreateFileModal.tsx b/karavan-web/karavan-app/src/main/webui/src/project/files/CreateFileModal.tsx
index 60723466..53473734 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/files/CreateFileModal.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/files/CreateFileModal.tsx
@@ -22,7 +22,9 @@ import {
     FormGroup,
     ModalVariant,
     Form,
-    ToggleGroupItem, ToggleGroup, FormHelperText, HelperText, HelperTextItem, TextInput
+    ToggleGroupItem, ToggleGroup, FormHelperText, HelperText, HelperTextItem, TextInput, Select,
+    SelectOption,
+    SelectList, MenuToggleElement, MenuToggle, TextInputGroup, TextInputGroupMain, TextInputGroupUtilities,
 } from '@patternfly/react-core';
 import '../../designer/karavan.css';
 import {Integration, KameletTypes, MetadataLabels} from "karavan-core/lib/model/IntegrationDefinition";
@@ -33,19 +35,23 @@ import {CamelUi} from "../../designer/utils/CamelUi";
 import {ProjectService} from "../../api/ProjectService";
 import {shallow} from "zustand/shallow";
 import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
+import {KameletApi} from "karavan-core/lib/api/KameletApi";
 
 interface Props {
     types: string[],
     isKameletsProject: boolean
 }
 
-export function CreateFileModal (props: Props) {
+export function CreateFileModal(props: Props) {
 
     const [project] = useProjectStore((s) => [s.project], shallow);
     const [operation, setFile] = useFileStore((s) => [s.operation, s.setFile], shallow);
-    const [name, setName] = useState<string>( '');
+    const [name, setName] = useState<string>('');
     const [fileType, setFileType] = useState<string>();
     const [kameletType, setKameletType] = useState<KameletTypes>('source');
+    const [inputValue, setInputValue] = useState<string>();
+    const [selectIsOpen, setSelectIsOpen] = useState<boolean>(false);
+    const [selectedKamelet, setSelectedKamelet] = useState<[string, string]>(['', '']);
 
     useEffect(() => {
         if (props.types.length > 0) {
@@ -53,12 +59,12 @@ export function CreateFileModal (props: Props) {
         }
     }, [props]);
 
-    function cleanValues()  {
+    function cleanValues() {
         setName("");
         setFileType(props.types.at(0) || 'INTEGRATION');
     }
 
-    function closeModal () {
+    function closeModal() {
         setFile("none");
         cleanValues();
     }
@@ -67,9 +73,10 @@ export function CreateFileModal (props: Props) {
         if (fileType === 'INTEGRATION') {
             return CamelDefinitionYaml.integrationToYaml(Integration.createNew(name, 'plain'));
         } else if (fileType === 'KAMELET') {
-            const kameletName = name + (isKamelet ? '-'+kameletType : '');
+            console.log(selectedKamelet, inputValue);
+            const kameletName = name + (isKamelet ? '-' + kameletType : '');
             const integration = Integration.createNew(kameletName, 'kamelet');
-            const meta:MetadataLabels = new MetadataLabels({"camel.apache.org/kamelet.type": kameletType});
+            const meta: MetadataLabels = new MetadataLabels({"camel.apache.org/kamelet.type": kameletType});
             integration.metadata.labels = meta;
             return CamelDefinitionYaml.integrationToYaml(integration);
         } else {
@@ -77,12 +84,12 @@ export function CreateFileModal (props: Props) {
         }
     }
 
-    function confirmAndCloseModal () {
+    function confirmAndCloseModal() {
         const extension = ProjectFileTypes.filter(value => value.name === fileType)[0].extension;
         const filename = (extension !== 'java') ? fileNameCheck(name) : CamelUi.javaNameFromTitle(name);
         const code = getCode();
         if (filename && extension) {
-            const fullFileName = filename + (isKamelet ? '-'+kameletType : '') + '.' + extension;
+            const fullFileName = filename + (isKamelet ? '-' + kameletType : '') + '.' + extension;
             const file = new ProjectFile(fullFileName, project.projectId, code, Date.now());
             ProjectService.createFile(file);
             cleanValues();
@@ -94,7 +101,7 @@ export function CreateFileModal (props: Props) {
         }
     }
 
-    function fileNameCheck (title: string) {
+    function fileNameCheck(title: string) {
         return title.replace(/[^0-9a-zA-Z.]+/gi, "-").toLowerCase();
     }
 
@@ -103,10 +110,57 @@ export function CreateFileModal (props: Props) {
     const filename = (extension !== 'java')
         ? fileNameCheck(name)
         : CamelUi.javaNameFromTitle(name);
-    const fullFileName = filename + (isKamelet ? '-'+kameletType : '') + '.' + extension;
+    const fullFileName = filename + (isKamelet ? '-' + kameletType : '') + '.' + extension;
+
+    const selectOptions: React.JSX.Element[] = []
+    KameletApi.getKamelets()
+        .filter(k => k.metadata.labels["camel.apache.org/kamelet.type"] === kameletType)
+        .filter(k => k.spec.definition.title.toLowerCase().includes(inputValue?.toLowerCase() || ''))
+        .forEach((kamelet) => {
+        const s = <SelectOption key={kamelet.metadata.name}
+                                value={kamelet.metadata.name}
+                                description={kamelet.spec.definition.title}
+                                isFocused={kamelet.metadata.name === selectedKamelet[0]}
+                                onClick={event => {
+                                    setSelectedKamelet([kamelet.metadata.name, kamelet.spec.definition.title]);
+                                    setSelectIsOpen(false);
+                                    setInputValue(kamelet.spec.definition.title)
+                                }}
+        />;
+        selectOptions.push(s);
+    })
+
+
+    const toggle = (toggleRef: React.Ref<MenuToggleElement>) => (
+        <MenuToggle variant={"typeahead"}
+            isFullWidth
+            ref={toggleRef}
+            onClick={() => setSelectIsOpen(!selectIsOpen)}
+            isExpanded={selectIsOpen}
+            isDisabled={false}>
+            <TextInputGroup isPlain>
+                <TextInputGroupMain
+                    value={inputValue}
+                    onClick={event => {}}
+                    onChange={(event, value) => {
+                        setInputValue(value);
+                        setSelectIsOpen(true)
+                    }}
+                    id="typeahead-select-input"
+                    autoComplete="off"
+                    // innerRef={textInputRef}
+                    placeholder="Select Kamelet"
+                    role="combobox"
+                    isExpanded={selectIsOpen}
+                    aria-controls="select-typeahead-listbox"
+                />
+            </TextInputGroup>
+        </MenuToggle>
+    );
+
     return (
         <Modal
-            title="Create"
+            title={"Create " + (isKamelet ? "Kamelet" : "")}
             variant={ModalVariant.small}
             isOpen={["create", "copy"].includes(operation)}
             onClose={closeModal}
@@ -123,13 +177,13 @@ export function CreateFileModal (props: Props) {
                                 const title = p.title + ' (' + p.extension + ')';
                                 return <ToggleGroupItem key={title} text={title} buttonId={p.name}
                                                         isSelected={fileType === p.name}
-                                                        onChange={(_, selected)  => {
+                                                        onChange={(_, selected) => {
                                                             setFileType(p.name);
                                                         }}/>
                             })}
                     </ToggleGroup>
                 </FormGroup>}
-                {isKamelet && <FormGroup label="Kamelet Type" fieldId="kameletType" isRequired>
+                {isKamelet && <FormGroup label="Type" fieldId="kameletType" isRequired>
                     <ToggleGroup aria-label="Kamelet Type">
                         {['source', 'action', 'sink'].map((type) => {
                             const title = CamelUtil.capitalizeName(type);
@@ -137,18 +191,33 @@ export function CreateFileModal (props: Props) {
                                                     isSelected={kameletType === type}
                                                     onChange={(_, selected) => {
                                                         setKameletType(type as KameletTypes);
+                                                        setSelectedKamelet(['', ''])
                                                     }}/>
                         })}
                     </ToggleGroup>
                 </FormGroup>}
                 <FormGroup label="Name" fieldId="name" isRequired>
                     <TextInput id="name" aria-label="name" value={name} onChange={(_, value) => setName(value)}/>
-                    <FormHelperText  >
+                    <FormHelperText>
                         <HelperText id="helper-text1">
                             <HelperTextItem variant={'default'}>{fullFileName}</HelperTextItem>
                         </HelperText>
                     </FormHelperText>
                 </FormGroup>
+                {isKamelet && <FormGroup label="Copy from" fieldId="kamelet">
+                    <Select
+                        aria-label={"Kamelet"}
+                        onOpenChange={isOpen => setSelectIsOpen(false)}
+                        isOpen={selectIsOpen}
+                        aria-labelledby={"Kamelets"}
+                        toggle={toggle}
+                        shouldFocusToggleOnSelect
+                    >
+                        <SelectList>
+                            {selectOptions}
+                        </SelectList>
+                    </Select>
+                </FormGroup>}
             </Form>
         </Modal>
     )