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 454e0a88006430202a95746267f9743dc0a410f2
Author: Marat Gubaidullin <ma...@talismancloud.io>
AuthorDate: Wed Feb 14 10:50:02 2024 -0500

    Incoming navigation for #1109
---
 karavan-designer/public/example/demo.camel.yaml    |   8 +-
 karavan-designer/src/App.tsx                       | 108 +++++++++------------
 karavan-designer/src/DesignerPage.tsx              |  11 +--
 karavan-designer/src/designer/KaravanDesigner.tsx  |   2 +-
 .../src/designer/route/DslConnections.tsx          |  65 ++++++++++---
 .../src/designer/utils/InfrastructureAPI.ts        |   4 +-
 karavan-designer/src/topology/TopologyStore.ts     |  14 +++
 karavan-designer/src/topology/TopologyTab.tsx      |  11 +--
 8 files changed, 131 insertions(+), 92 deletions(-)

diff --git a/karavan-designer/public/example/demo.camel.yaml 
b/karavan-designer/public/example/demo.camel.yaml
index 9d74d5d4..28340ccf 100644
--- a/karavan-designer/public/example/demo.camel.yaml
+++ b/karavan-designer/public/example/demo.camel.yaml
@@ -44,7 +44,7 @@
             id: to-2df4
             uri: direct
             parameters:
-              name: first-firect
+              name: second_direct
         - to:
             id: to-e017
             uri: direct
@@ -57,6 +57,12 @@
       uri: direct
       parameters:
         name: first-firect
+      steps:
+        - to:
+            id: to-5c86
+            uri: direct
+            parameters:
+              name: second_direct
 - route:
     id: second_direct
     from:
diff --git a/karavan-designer/src/App.tsx b/karavan-designer/src/App.tsx
index a6613131..9217fa82 100644
--- a/karavan-designer/src/App.tsx
+++ b/karavan-designer/src/App.tsx
@@ -41,7 +41,9 @@ import {KnowledgebasePage} from 
"./knowledgebase/KnowledgebasePage";
 import {Notification} from "./designer/utils/Notification";
 import {EventBus} from "./designer/utils/EventBus";
 import {TopologyTab} from "./topology/TopologyTab";
-import {IntegrationFile} from "./topology/TopologyStore";
+import {useEffect, useState} from "react";
+import {IntegrationFile, useTopologyStore} from "./topology/TopologyStore";
+import {shallow} from "zustand/shallow";
 
 class MenuItem {
     pageId: string = '';
@@ -55,27 +57,16 @@ class MenuItem {
     }
 }
 
-interface Props {
-}
-
-interface State {
-    name: string
-    yaml: string
-    key: string
-    loaded?: boolean,
-    pageId: string,
-}
+export function App() {
 
-class App extends React.Component<Props, State> {
+    const [setFiles] = useTopologyStore((s) => [s.setFiles], shallow);
+    const [pageId, setPageId] = useState<string>('designer');
+    const [name, setName] = useState<string>('example.yaml');
+    const [key, setKey] = useState<string>('');
+    const [yaml, setYaml] = useState<string>('');
+    const [loaded, setLoaded] = useState<boolean>(false);
 
-    public state: State = {
-        pageId: "designer",
-        name: 'example.yaml',
-        key: '',
-        yaml: ''
-    }
-
-    componentDidMount() {
+    useEffect(() => {
         Promise.all([
             fetch("kamelets/kamelets.yaml"),
             fetch("components/components.json"),
@@ -96,14 +87,15 @@ class App extends React.Component<Props, State> {
             JSON.parse(data[1]).forEach((c: any) => 
jsons.push(JSON.stringify(c)));
             ComponentApi.saveComponents(jsons, true);
 
-            this.setState({loaded: true});
+            setLoaded(true);
 
             TemplateApi.saveTemplate("org.apache.camel.AggregationStrategy", 
data[2]);
             TemplateApi.saveTemplate("org.apache.camel.Processor", data[3]);
 
             if (data[4]) {
-                this.setState({yaml: data[4], name: "demo.camel.yaml"})
-                // this.setState({yaml: data[4], name: 
"aws-s3-cdc-source.kamelet.yaml"})
+                setYaml(data[4]);
+                setName("demo.camel.yaml");
+                setFiles([new IntegrationFile("demo.camel.yaml", yaml)]);
             }
 
             if (data[5]) {
@@ -113,13 +105,13 @@ class App extends React.Component<Props, State> {
         }).catch(err =>
             EventBus.sendAlert("Error", err.text, 'danger')
         );
-    }
+    });
 
-    save(filename: string, yaml: string, propertyOnly: boolean) {
-        console.log(yaml);
+    function save(filename: string, yaml: string, propertyOnly: boolean) {
+        // console.log(yaml);
     }
 
-    getSpinner() {
+    function getSpinner() {
         return (
             <Bullseye className="loading-page">
                 <Spinner className="progress-stepper"  diameter="80px" 
aria-label="Loading..."/>
@@ -127,8 +119,7 @@ class App extends React.Component<Props, State> {
         )
     }
 
-    pageNav = () => {
-        const {pageId} = this.state;
+    function pageNav ()  {
         const pages: MenuItem[] = [
             new MenuItem("designer", "Designer", <BlueprintIcon/>),
             new MenuItem("topology", "Topology", <TopologyIcon/>),
@@ -147,7 +138,7 @@ class App extends React.Component<Props, State> {
                     <Tooltip content={page.tooltip} position={"right"}>
                         <Button id={page.pageId} icon={page.icon} 
variant={"plain"}
                                 className={pageId === page.pageId ? 
"nav-button-selected" : ""}
-                                onClick={event => this.setState({pageId: 
page.pageId})}
+                                onClick={event => setPageId(page.pageId)}
                         />
                     </Tooltip>
                 </FlexItem>
@@ -158,12 +149,7 @@ class App extends React.Component<Props, State> {
         </Flex>)
     }
 
-    getIntegrationFiles(): IntegrationFile[]{
-        return [new IntegrationFile("demo.camel.yaml", this.state.yaml)];
-    }
-
-    getPage() {
-        const { name, yaml, pageId} = this.state;
+    function getPage() {
         const dark = document.body.className.includes('vscode-dark');
         switch (pageId) {
             case "designer":
@@ -171,7 +157,7 @@ class App extends React.Component<Props, State> {
                     <DesignerPage
                         name={name}
                         yaml={yaml}
-                        onSave={(filename, yaml1, propertyOnly) => 
this.save(filename, yaml1, propertyOnly)}
+                        onSave={(filename, yaml1, propertyOnly) => 
save(filename, yaml1, propertyOnly)}
                         dark={dark}/>
                 )
             case "knowledgebase":
@@ -181,7 +167,6 @@ class App extends React.Component<Props, State> {
             case "topology":
                 return (
                     <TopologyTab
-                        files={this.getIntegrationFiles()}
                         onSetFile={fileName => {}}
                         onClickAddRoute={() => {}}
                         onClickAddREST={() => {}}
@@ -192,35 +177,32 @@ class App extends React.Component<Props, State> {
         }
     }
 
-    getHeader = () => (
-        <Masthead>
-        </Masthead>
-    );
+    function getHeader () {
+        return (<Masthead>
+        </Masthead>)
+    }
 
-    getSidebar = () => (
-        <PageSidebar isSidebarOpen={true} id="fill-sidebar">
+    function getSidebar () {
+        return (<PageSidebar isSidebarOpen={true} id="fill-sidebar">
             <PageSidebarBody>Navigation</PageSidebarBody>
-        </PageSidebar>
-    );
+        </PageSidebar>)
+    }
 
-    public render() {
-        const {loaded} = this.state;
-        return (
-            <Page className="karavan">
-                <Notification/>
-                <Flex direction={{default: "row"}} style={{width: "100%", 
height: "100%"}}
-                      alignItems={{default: "alignItemsStretch"}} 
spaceItems={{default: 'spaceItemsNone'}}>
-                    <FlexItem>
-                        {this.pageNav()}
-                    </FlexItem>
-                    <FlexItem flex={{default: "flex_2"}} style={{height: 
"100%"}}>
-                        {loaded !== true && this.getSpinner()}
-                        {loaded === true && this.getPage()}
+    return (
+        <Page className="karavan">
+            <Notification/>
+            <Flex direction={{default: "row"}} style={{width: "100%", height: 
"100%"}}
+                  alignItems={{default: "alignItemsStretch"}} 
spaceItems={{default: 'spaceItemsNone'}}>
+                <FlexItem>
+                    {pageNav()}
                 </FlexItem>
-                </Flex>
-            </Page>
-        )
-    }
+                <FlexItem flex={{default: "flex_2"}} style={{height: "100%"}}>
+                    {!loaded && getSpinner()}
+                    {loaded && getPage()}
+                </FlexItem>
+            </Flex>
+        </Page>
+    )
 }
 
 export default App;
diff --git a/karavan-designer/src/DesignerPage.tsx 
b/karavan-designer/src/DesignerPage.tsx
index 56bde9e3..4a30e899 100644
--- a/karavan-designer/src/DesignerPage.tsx
+++ b/karavan-designer/src/DesignerPage.tsx
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React, {useEffect, useState} from 'react';
+import React, {useState} from 'react';
 import {
     Toolbar,
     ToolbarContent,
@@ -38,11 +38,6 @@ export const DesignerPage = (props: Props) => {
 
     const [yaml, setYaml] = useState<string>(props.yaml);
 
-    useEffect(() => {
-        console.log("DesignerPage")
-        // setYaml();
-    }, []);
-
     function save(filename: string, yaml: string, propertyOnly: boolean) {
         setYaml(yaml);
         props.onSave(filename, yaml, propertyOnly);
@@ -82,8 +77,8 @@ export const DesignerPage = (props: Props) => {
                 ]}
                 onSavePropertyPlaceholder={(key, value) => 
console.log("onSavePropertyPlaceholder", key, value)}
                 beans={[]}
-                onInternalConsumerClick={(uri, name) => {
-                    console.log("onInternalConsumerClick", uri, name)
+                onInternalConsumerClick={(uri, name, direction: 'from' | 'to') 
=> {
+                    console.log("onInternalConsumerClick", uri, name, 
direction)
                 }}
             />
         )
diff --git a/karavan-designer/src/designer/KaravanDesigner.tsx 
b/karavan-designer/src/designer/KaravanDesigner.tsx
index 05831a38..75e6c012 100644
--- a/karavan-designer/src/designer/KaravanDesigner.tsx
+++ b/karavan-designer/src/designer/KaravanDesigner.tsx
@@ -48,7 +48,7 @@ interface Props {
     onSaveCustomCode: (name: string, code: string) => void
     onGetCustomCode: (name: string, javaType: string) => Promise<string | 
undefined>
     onSavePropertyPlaceholder: (key: string, value: string) => void
-    onInternalConsumerClick: (uri: string, name: string) => void
+    onInternalConsumerClick: (uri: string, name: string, direction: 'from' | 
'to') => void
     filename: string
     yaml: string
     dark: boolean
diff --git a/karavan-designer/src/designer/route/DslConnections.tsx 
b/karavan-designer/src/designer/route/DslConnections.tsx
index ebb89933..cab339f8 100644
--- a/karavan-designer/src/designer/route/DslConnections.tsx
+++ b/karavan-designer/src/designer/route/DslConnections.tsx
@@ -24,28 +24,46 @@ import {CamelDefinitionApiExt} from 
"karavan-core/lib/api/CamelDefinitionApiExt"
 import {TopologyUtils} from "karavan-core/lib/api/TopologyUtils";
 import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
 import {v4 as uuidv4} from "uuid";
-import {DeleteElementIcon} from "../utils/ElementIcons";
-import {Button} from "@patternfly/react-core";
+import {Button, Tooltip} from "@patternfly/react-core";
 import {InfrastructureAPI} from "../utils/InfrastructureAPI";
+import {useTopologyStore} from "../../topology/TopologyStore";
+import {getIntegrations} from "../../topology/TopologyApi";
 
 const overlapGap: number = 40;
 
 export function DslConnections() {
 
     const [integration] = useIntegrationStore((state) => [state.integration], 
shallow)
+    const [files] = useTopologyStore((s) => [s.files], shallow);
     const [width, height, top, left, hideLogDSL] = useDesignerStore((s) =>
         [s.width, s.height, s.top, s.left, s.hideLogDSL], shallow)
     const [steps, addStep, deleteStep, clearSteps] =
         useConnectionsStore((s) => [s.steps, s.addStep, s.deleteStep, 
s.clearSteps], shallow)
 
     const [svgKey, setSvgKey] = useState<string>('svgKey');
+    const [tons, setTons] = useState<Map<string, string[]>>(new Map<string, 
string[]>());
 
     useEffect(() => {
+        const integrations = getIntegrations(files);
+        setTons(prevState => {
+            const data = new Map<string, string[]>();
+            TopologyUtils.findTopologyOutgoingNodes(integrations).forEach(t => 
{
+                const key = (t.step as any)?.uri + ':' + (t.step as 
any)?.parameters.name;
+                if (data.has(key)) {
+                    const list = data.get(key) || [];
+                    list.push(t.routeId);
+                    data.set(key, list);
+                } else {
+                    data.set(key, [t.routeId]);
+                }
+            });
+            return data;
+        });
         const sub1 = EventBus.onPosition()?.subscribe((evt: DslPosition) => 
setPosition(evt));
         return () => {
             sub1?.unsubscribe();
         };
-    });
+    }, [files]);
 
     useEffect(() => {
         const toDelete1: string[] = Array.from(steps.keys()).filter(k => 
CamelDefinitionApiExt.findElementInIntegration(integration, k) === undefined);
@@ -67,7 +85,7 @@ export function DslConnections() {
         let outs: [string, number][] = Array.from(steps.values())
             .filter(pos => ["FromDefinition"].includes(pos.step.dslName))
             .filter(pos => !TopologyUtils.isElementInternalComponent(pos.step))
-            .filter(pos => !(pos.step.dslName === 'FromDefinition' && 
TopologyUtils.hasInternalUri(pos.step)))
+            // .filter(pos => !(pos.step.dslName === 'FromDefinition' && 
TopologyUtils.hasInternalUri(pos.step)))
             .filter(pos => !(pos.step.dslName === 'FromDefinition' && 
(pos.step as any).uri === 'kamelet:source'))
             .sort((pos1: DslPosition, pos2: DslPosition) => {
                 const y1 = pos1.headerRect.y + pos1.headerRect.height / 2;
@@ -104,9 +122,22 @@ export function DslConnections() {
         }
     }
 
+    function getToDirectSteps(name: string) {
+        return Array.from(steps.values())
+            .filter(s => s.step.dslName === 'ToDefinition')
+            .filter(s =>  ['direct','seda'].includes((s.step as any)?.uri))
+            .filter(s =>  (s.step as any)?.parameters?.name === name)
+    }
+
     function getIncomingIcons(data: [string, number]) {
         const pos = steps.get(data[0]);
         if (pos) {
+            const step = (pos.step as any);
+            const uri = step?.uri;
+            const directOrSeda: boolean = step && uri && step?.dslName === 
'FromDefinition' && ['direct','seda'].includes(uri);
+            const name: string = directOrSeda ? (step?.parameters?.name) : 
undefined;
+            const routes = directOrSeda ? tons.get(uri + ':' +name) || [] : [];
+            const localDirects = getToDirectSteps(name);
             const fromY = pos.headerRect.y + pos.headerRect.height / 2 - top;
             const r = pos.headerRect.height / 2;
             const incomingX = 20;
@@ -116,6 +147,16 @@ export function DslConnections() {
                 <div key={pos.step.uuid + "-icon"}
                      style={{display: "block", position: "absolute", top: 
imageY, left: imageX}}>
                     {CamelUi.getConnectionIcon(pos.step)}
+                    {routes.map((routeId, index) =>
+                        <Tooltip key={`${routeId}:${index}`} content={`Go to 
route:${routeId}`} position={"right"}>
+                            <Button style={{position: 'absolute', left: 27, 
top: (index * 16) + (12), whiteSpace: 'nowrap', zIndex: 300, padding: 0}}
+                                    variant={'link'}
+                                    aria-label="Goto"
+                                    onClick={_ => 
InfrastructureAPI.onInternalConsumerClick(uri, name, 'from')}>
+                                {routeId}
+                            </Button>
+                        </Tooltip>
+                    )}
                 </div>
             )
         }
@@ -204,13 +245,15 @@ export function DslConnections() {
                 <div key={pos.step.uuid + "-icon"}
                      style={{display: "block", position: "absolute", top: 
imageY, left: imageX}}>
                     {CamelUi.getConnectionIcon(pos.step)}
-                    {directOrSeda &&
-                        <Button style={{position: 'absolute', right: 27, top: 
-12, whiteSpace: 'nowrap', zIndex: 300, padding: 0}}
-                               variant={'link'}
-                                aria-label="Goto"
-                                onClick={_ => 
InfrastructureAPI.onInternalConsumerClick(uri, name)}>
-                            {name}
-                        </Button>
+                    {name !== undefined &&
+                        <Tooltip content={`Go to ${uri}:${name}`} 
position={"left"}>
+                            <Button style={{position: 'absolute', right: 27, 
top: -12, whiteSpace: 'nowrap', zIndex: 300, padding: 0}}
+                                   variant={'link'}
+                                    aria-label="Goto"
+                                    onClick={_ => 
InfrastructureAPI.onInternalConsumerClick(uri, name, 'to')}>
+                                {name}
+                            </Button>
+                        </Tooltip>
                     }
                 </div>
             )
diff --git a/karavan-designer/src/designer/utils/InfrastructureAPI.ts 
b/karavan-designer/src/designer/utils/InfrastructureAPI.ts
index 77ef1bd3..2acd5d54 100644
--- a/karavan-designer/src/designer/utils/InfrastructureAPI.ts
+++ b/karavan-designer/src/designer/utils/InfrastructureAPI.ts
@@ -21,7 +21,7 @@ export class InfrastructureAPI {
     static onSaveCustomCode: (name: string, code: string) => void;
     static onSave: (filename: string, yaml: string, propertyOnly: boolean) => 
void;
     static onSavePropertyPlaceholder: (key: string, value: string) => void;
-    static onInternalConsumerClick: (uri: string, name: string) => void;
+    static onInternalConsumerClick: (uri: string, name: string, direction: 
'from' | 'to') => void;
 
     static setOnGetCustomCode(onGetCustomCode: (name: string, javaType: 
string) => Promise<string | undefined>){
         this.onGetCustomCode = onGetCustomCode
@@ -39,7 +39,7 @@ export class InfrastructureAPI {
         this.onSavePropertyPlaceholder = onSavePropertyPlaceholder
     }
 
-    static setOnInternalConsumerClick(onInternalConsumerClick:(uri: string, 
name: string) => void){
+    static setOnInternalConsumerClick(onInternalConsumerClick:(uri: string, 
name: string, direction: 'from' | 'to') => void){
         this.onInternalConsumerClick = onInternalConsumerClick
     }
 
diff --git a/karavan-designer/src/topology/TopologyStore.ts 
b/karavan-designer/src/topology/TopologyStore.ts
index 4e9ee643..c6322f39 100644
--- a/karavan-designer/src/topology/TopologyStore.ts
+++ b/karavan-designer/src/topology/TopologyStore.ts
@@ -29,6 +29,9 @@ export class IntegrationFile {
 }
 
 interface TopologyState {
+    files: IntegrationFile []
+    setFiles: (files: IntegrationFile []) => void
+    resetFiles: (files: IntegrationFile []) => void
     selectedIds: string []
     fileName?: string
     setSelectedIds: (selectedIds: string []) => void
@@ -40,6 +43,17 @@ interface TopologyState {
 }
 
 export const useTopologyStore = createWithEqualityFn<TopologyState>((set) => ({
+    files: [],
+    setFiles: (files: IntegrationFile []) => {
+        set((state: TopologyState) => {
+            return {files: files};
+        });
+    },
+    resetFiles: (files: IntegrationFile []) => {
+        set((state: TopologyState) => {
+            return {files: [...files]};
+        });
+    },
     selectedIds: [],
     setSelectedIds: (selectedIds: string[]) => {
         set((state: TopologyState) => {
diff --git a/karavan-designer/src/topology/TopologyTab.tsx 
b/karavan-designer/src/topology/TopologyTab.tsx
index 1f2d165e..bf16124a 100644
--- a/karavan-designer/src/topology/TopologyTab.tsx
+++ b/karavan-designer/src/topology/TopologyTab.tsx
@@ -37,7 +37,6 @@ import {TopologyToolbar} from "./TopologyToolbar";
 import {useDesignerStore} from "../designer/DesignerStore";
 
 interface Props {
-    files: IntegrationFile[],
     onSetFile: (fileName: string) => void
     hideToolbar: boolean
     onClickAddRoute: () => void
@@ -47,8 +46,8 @@ interface Props {
 
 export function TopologyTab(props: Props) {
 
-    const [selectedIds, setSelectedIds, setFileName, ranker, setRanker, 
setNodeData] = useTopologyStore((s) =>
-        [s.selectedIds, s.setSelectedIds, s.setFileName, s.ranker, 
s.setRanker, s.setNodeData], shallow);
+    const [selectedIds, setSelectedIds, setFileName, ranker, setRanker, 
setNodeData, files] = useTopologyStore((s) =>
+        [s.selectedIds, s.setSelectedIds, s.setFileName, s.ranker, 
s.setRanker, s.setNodeData, s.files], shallow);
     const [setSelectedStep] = useDesignerStore((s) => [s.setSelectedStep], 
shallow)
 
     function setTopologySelected(model: Model, ids: string []) {
@@ -70,7 +69,7 @@ export function TopologyTab(props: Props) {
     }
 
     const controller = React.useMemo(() => {
-        const model = getModel(props.files);
+        const model = getModel(files);
         const newController = new Visualization();
         newController.registerLayoutFactory((_, graph) =>
             new DagreLayout(graph, {
@@ -97,9 +96,9 @@ export function TopologyTab(props: Props) {
 
     React.useEffect(() => {
         setSelectedIds([])
-        const model = getModel(props.files);
+        const model = getModel(files);
         controller.fromModel(model, false);
-    }, [ranker, controller, setSelectedIds, props.files]);
+    }, [ranker, controller, setSelectedIds, files]);
 
     const controlButtons = React.useMemo(() => {
         // const customButtons = [

Reply via email to