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 ad3a375e Navigation improvements ad3a375e is described below commit ad3a375e4109969eef864845beb612330ae8b340 Author: Marat Gubaidullin <ma...@talismancloud.io> AuthorDate: Fri Mar 1 19:07:00 2024 -0500 Navigation improvements --- karavan-app/src/main/webui/src/api/ProjectStore.ts | 8 +- .../src/main/webui/src/editor/CodeEditor.tsx | 68 ++++++++++++ .../FileEditor.tsx => editor/DesignerEditor.tsx} | 52 ++------- .../EditorToolbar.tsx} | 56 +++------- .../src/main/webui/src/editor/FileEditor.tsx | 50 +++++++++ .../src/main/webui/src/project/ProjectPage.tsx | 24 +++-- .../src/main/webui/src/project/ProjectPanel.tsx | 2 +- .../src/main/webui/src/project/ProjectTitle.tsx | 117 +++++++++++++-------- .../src/main/webui/src/project/ProjectToolbar.tsx | 20 +--- 9 files changed, 237 insertions(+), 160 deletions(-) diff --git a/karavan-app/src/main/webui/src/api/ProjectStore.ts b/karavan-app/src/main/webui/src/api/ProjectStore.ts index ccbb0399..861a0c06 100644 --- a/karavan-app/src/main/webui/src/api/ProjectStore.ts +++ b/karavan-app/src/main/webui/src/api/ProjectStore.ts @@ -130,7 +130,7 @@ export const useProjectStore = createWithEqualityFn<ProjectState>((set) => ({ project: new Project(), images: [], operation: 'none', - tabIndex: 'files', + tabIndex: 'topology', isPushing: false, isPulling: false, isRunning: false, @@ -152,9 +152,9 @@ export const useProjectStore = createWithEqualityFn<ProjectState>((set) => ({ })); }, setTabIndex: (tabIndex: string | number) => { - set((state: ProjectState) => ({ - tabIndex: tabIndex - })); + set((state: ProjectState) => { + return {tabIndex: tabIndex}; + }); }, setImages: (images: string[]) => { set((state: ProjectState) => { diff --git a/karavan-app/src/main/webui/src/editor/CodeEditor.tsx b/karavan-app/src/main/webui/src/editor/CodeEditor.tsx new file mode 100644 index 00000000..3658889b --- /dev/null +++ b/karavan-app/src/main/webui/src/editor/CodeEditor.tsx @@ -0,0 +1,68 @@ +/* + * 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, {useEffect, useState} from 'react'; +import '../designer/karavan.css'; +import Editor from "@monaco-editor/react"; +import {useFileStore} from "../api/ProjectStore"; +import {ProjectService} from "../api/ProjectService"; +import {shallow} from "zustand/shallow"; + +interface Props { + projectId: string +} + +const languages = new Map<string, string>([ + ['sh', 'shell'], + ['md', 'markdown'], + ['properties', 'ini'] +]) + +export function CodeEditor(props: Props) { + + const [file, designerTab, setFile] = useFileStore((s) => [s.file, s.designerTab, s.setFile], shallow) + const [code, setCode] = useState<string>(); + + useEffect(() => { + setCode(file?.code); + }, []); + + function save(name: string, code: string) { + if (file) { + file.code = code; + ProjectService.updateFile(file, true); + } + } + + const extension = file?.name.split('.').pop(); + const language = extension && languages.has(extension) ? languages.get(extension) : extension; + return ( + file !== undefined ? + <Editor + height="100vh" + defaultLanguage={language} + theme={'light'} + value={code} + className={'code-editor'} + onChange={(value, ev) => { + if (value) { + save(file?.name, value) + } + }} + /> + : <></> + ) +} diff --git a/karavan-app/src/main/webui/src/project/FileEditor.tsx b/karavan-app/src/main/webui/src/editor/DesignerEditor.tsx similarity index 79% rename from karavan-app/src/main/webui/src/project/FileEditor.tsx rename to karavan-app/src/main/webui/src/editor/DesignerEditor.tsx index 95a1a195..a4391f72 100644 --- a/karavan-app/src/main/webui/src/project/FileEditor.tsx +++ b/karavan-app/src/main/webui/src/editor/DesignerEditor.tsx @@ -17,7 +17,6 @@ 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 {useFilesStore, useFileStore} from "../api/ProjectStore"; import {KaravanDesigner} from "../designer/KaravanDesigner"; @@ -25,21 +24,16 @@ import {ProjectService} from "../api/ProjectService"; import {shallow} from "zustand/shallow"; import {CodeUtils} from "../util/CodeUtils"; import {RegistryBeanDefinition} from "karavan-core/lib/model/CamelDefinition"; +import {KameletApi} from "karavan-core/lib/api/KameletApi"; +import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml"; import {TopologyUtils} from "karavan-core/lib/api/TopologyUtils"; import {IntegrationFile} from "karavan-core/lib/model/IntegrationDefinition"; -import { KameletApi } from 'karavan-core/lib/api/KameletApi'; interface Props { projectId: string } -const languages = new Map<string, string>([ - ['sh', 'shell'], - ['md', 'markdown'], - ['properties', 'ini'] -]) - -export function FileEditor(props: Props) { +export function DesignerEditor(props: Props) { const [file, designerTab, setFile] = useFileStore((s) => [s.file, s.designerTab, s.setFile], shallow) const [files] = useFilesStore((s) => [s.files], shallow); @@ -67,7 +61,7 @@ export function FileEditor(props: Props) { if (props.projectId.includes('kamelets') && file) { KameletApi.saveKamelet(file?.code); } - }; + }; }, []); function save(name: string, code: string) { @@ -117,9 +111,7 @@ export function FileEditor(props: Props) { } } - function getDesigner() { - return ( - file !== undefined && + return (file !== undefined ? <KaravanDesigner key={key} showCodeTab={true} dark={false} @@ -136,38 +128,6 @@ export function FileEditor(props: Props) { onInternalConsumerClick={internalConsumerClick} files={files.map(f => new IntegrationFile(f.name, f.code))} /> - ) - } - - function getEditor() { - const extension = file?.name.split('.').pop(); - const language = extension && languages.has(extension) ? languages.get(extension) : extension; - return ( - file !== undefined && - <Editor - height="100vh" - defaultLanguage={language} - theme={'light'} - value={code} - className={'code-editor'} - onChange={(value, ev) => { - if (value) { - save(file?.name, value) - } - }} - /> - ) - } - - const isCamelYaml = file !== undefined && file.name.endsWith(".camel.yaml"); - const isKameletYaml = file !== undefined && file.name.endsWith(".kamelet.yaml"); - const isIntegration = isCamelYaml && file?.code && CamelDefinitionYaml.yamlIsIntegration(file.code); - const showDesigner = (isCamelYaml && isIntegration) || isKameletYaml; - const showEditor = !showDesigner; - return ( - <> - {showDesigner && getDesigner()} - {showEditor && getEditor()} - </> + : <></> ) } diff --git a/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx b/karavan-app/src/main/webui/src/editor/EditorToolbar.tsx similarity index 57% copy from karavan-app/src/main/webui/src/project/ProjectToolbar.tsx copy to karavan-app/src/main/webui/src/editor/EditorToolbar.tsx index a3c9471a..f583fa00 100644 --- a/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx +++ b/karavan-app/src/main/webui/src/editor/EditorToolbar.tsx @@ -23,12 +23,11 @@ import { ToolbarContent, } from '@patternfly/react-core'; import '../designer/karavan.css'; -import {DevModeToolbar} from "./DevModeToolbar"; import {useFileStore, useProjectStore} from "../api/ProjectStore"; import {shallow} from "zustand/shallow"; -import {BuildToolbar} from "./BuildToolbar"; +import {DevModeToolbar} from "../project/DevModeToolbar"; -export function ProjectToolbar() { +export function EditorToolbar() { const [project, tabIndex] = useProjectStore((s) => [s.project, s.tabIndex], shallow) const [file] = useFileStore((state) => [state.file], shallow) @@ -36,37 +35,6 @@ export function ProjectToolbar() { useEffect(() => { }, [project, file]); - function isFile(): boolean { - return file !== undefined; - } - - function getFileToolbar() { - return ( - <Toolbar id="toolbar-group-types"> - <ToolbarContent> - <Flex className="" direction={{default: "row"}} - justifyContent={{default: 'justifyContentSpaceBetween'}} - alignItems={{default: "alignItemsCenter"}}> - {isRunnable() && - <FlexItem align={{default: 'alignRight'}}> - <DevModeToolbar reloadOnly={true}/> - </FlexItem> - } - </Flex> - </ToolbarContent> - </Toolbar> - ) - } - - function getProjectToolbar() { - return (<Toolbar id="toolbar-group-types"> - <ToolbarContent> - {isRunnable() && <DevModeToolbar/>} - {isBuildContainer() && <BuildToolbar/>} - </ToolbarContent> - </Toolbar>) - } - function isKameletsProject(): boolean { return project.projectId === 'kamelets'; } @@ -83,9 +51,19 @@ export function ProjectToolbar() { return !isKameletsProject() && !isTemplatesProject() && !isServicesProject() && !['build', 'container'].includes(tabIndex.toString()); } - function isBuildContainer(): boolean { - return !isKameletsProject() && !isTemplatesProject() && !isServicesProject() && ['build', 'container'].includes(tabIndex.toString()); - } - - return isFile() ? getFileToolbar() : getProjectToolbar() + return ( + <Toolbar id="toolbar-group-types"> + <ToolbarContent> + <Flex className="" direction={{default: "row"}} + justifyContent={{default: 'justifyContentSpaceBetween'}} + alignItems={{default: "alignItemsCenter"}}> + {isRunnable() && + <FlexItem align={{default: 'alignRight'}}> + <DevModeToolbar reloadOnly={true}/> + </FlexItem> + } + </Flex> + </ToolbarContent> + </Toolbar> + ) } diff --git a/karavan-app/src/main/webui/src/editor/FileEditor.tsx b/karavan-app/src/main/webui/src/editor/FileEditor.tsx new file mode 100644 index 00000000..2812d005 --- /dev/null +++ b/karavan-app/src/main/webui/src/editor/FileEditor.tsx @@ -0,0 +1,50 @@ +/* + * 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 '../designer/karavan.css'; +import {useFileStore} from "../api/ProjectStore"; +import {shallow} from "zustand/shallow"; +import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml"; +import {DesignerEditor} from "./DesignerEditor"; +import {CodeEditor} from "./CodeEditor"; + +interface Props { + projectId: string +} + +const languages = new Map<string, string>([ + ['sh', 'shell'], + ['md', 'markdown'], + ['properties', 'ini'] +]) + +export function FileEditor(props: Props) { + + const [file] = useFileStore((s) => [s.file], shallow) + + const isCamelYaml = file !== undefined && file.name.endsWith(".camel.yaml"); + const isKameletYaml = file !== undefined && file.name.endsWith(".kamelet.yaml"); + const isIntegration = isCamelYaml && file?.code && CamelDefinitionYaml.yamlIsIntegration(file.code); + const showDesigner = (isCamelYaml && isIntegration) || isKameletYaml; + const showEditor = !showDesigner; + return ( + <> + {showDesigner && <DesignerEditor projectId={props.projectId}/>} + {showEditor && <CodeEditor projectId={props.projectId}/>} + </> + ) +} diff --git a/karavan-app/src/main/webui/src/project/ProjectPage.tsx b/karavan-app/src/main/webui/src/project/ProjectPage.tsx index 6ed5ad4e..0773203d 100644 --- a/karavan-app/src/main/webui/src/project/ProjectPage.tsx +++ b/karavan-app/src/main/webui/src/project/ProjectPage.tsx @@ -29,7 +29,7 @@ import {useAppConfigStore, useFilesStore, useFileStore, useProjectsStore, usePro import {MainToolbar} from "../designer/MainToolbar"; import {ProjectTitle} from "./ProjectTitle"; import {ProjectPanel} from "./ProjectPanel"; -import {FileEditor} from "./FileEditor"; +import {FileEditor} from "../editor/FileEditor"; import {shallow} from "zustand/shallow"; import {useParams} from "react-router-dom"; import {KaravanApi} from "../api/KaravanApi"; @@ -91,15 +91,19 @@ export function ProjectPage() { <PageSection className="tools-section" padding={{default: 'noPadding'}}> <Flex direction={{default: "column"}} spaceItems={{default: "spaceItemsNone"}}> <FlexItem className="project-tabs"> - {showTabs() && <Tabs activeKey={tabIndex} onSelect={(event, tabIndex) => setTabIndex(tabIndex)}> - {!ephemeral && <Tab eventKey="topology" title="Topology"/>} - <Tab eventKey="files" title="Files"/> - {!ephemeral && <Tab eventKey="dashboard" title="Dashboard"/>} - {!ephemeral && <Tab eventKey="trace" title="Trace"/>} - {!ephemeral && <Tab eventKey="build" title="Build"/>} - <Tab eventKey="container" title="Container"/> - {hasReadme() && <Tab eventKey="readme" title="Readme"/>} - </Tabs>} + {showTabs() && + <Tabs activeKey={tabIndex} onSelect={(event, tabIndex) => { + setTabIndex(tabIndex); + }}> + {!ephemeral && <Tab eventKey="topology" title="Topology"/>} + <Tab eventKey="files" title="Files"/> + {!ephemeral && <Tab eventKey="dashboard" title="Dashboard"/>} + {!ephemeral && <Tab eventKey="trace" title="Trace"/>} + {!ephemeral && <Tab eventKey="build" title="Build"/>} + <Tab eventKey="container" title="Container"/> + {hasReadme() && <Tab eventKey="readme" title="Readme"/>} + </Tabs> + } </FlexItem> </Flex> </PageSection> diff --git a/karavan-app/src/main/webui/src/project/ProjectPanel.tsx b/karavan-app/src/main/webui/src/project/ProjectPanel.tsx index 238d6b47..c98e9d02 100644 --- a/karavan-app/src/main/webui/src/project/ProjectPanel.tsx +++ b/karavan-app/src/main/webui/src/project/ProjectPanel.tsx @@ -53,7 +53,7 @@ export function ProjectPanel() { function onRefresh() { if (project.projectId) { ProjectService.refreshProjectData(project.projectId); - setTab(project.type === ProjectType.normal ? 'topology' : 'files'); + setTab(project.type !== ProjectType.normal ? 'files' : tab); } } diff --git a/karavan-app/src/main/webui/src/project/ProjectTitle.tsx b/karavan-app/src/main/webui/src/project/ProjectTitle.tsx index da03f0fe..1c43c281 100644 --- a/karavan-app/src/main/webui/src/project/ProjectTitle.tsx +++ b/karavan-app/src/main/webui/src/project/ProjectTitle.tsx @@ -23,56 +23,89 @@ import { Text, TextContent, Flex, - FlexItem, + FlexItem, Button } from '@patternfly/react-core'; import '../designer/karavan.css'; import {getProjectFileType} from "../api/ProjectModels"; -import {useAppConfigStore, useFileStore, useProjectStore} from "../api/ProjectStore"; +import {useFileStore, useProjectStore} from "../api/ProjectStore"; +import TopologyIcon from "@patternfly/react-icons/dist/js/icons/topology-icon"; +import FilesIcon from "@patternfly/react-icons/dist/js/icons/folder-open-icon"; +import {shallow} from "zustand/shallow"; -export function ProjectTitle () { +export function ProjectTitle() { - const {project} = useProjectStore(); - const {file, operation, setFile} = useFileStore(); - const {config} = useAppConfigStore(); + const [project, tabIndex, setTabIndex] = + useProjectStore((s) => [s.project, s.tabIndex, s.setTabIndex], shallow); + const [file,setFile, operation] = useFileStore((s) => [s.file, s.setFile, s.operation], shallow); const isFile = file !== undefined; const isLog = file !== undefined && file.name.endsWith("log"); const filename = file ? file.name.substring(0, file.name.lastIndexOf('.')) : ""; - return (<div className="dsl-title project-title"> - {isFile && <Flex direction={{default: "column"}} > - <FlexItem> - <Breadcrumb> - <BreadcrumbItem to="#" onClick={event => { - useFileStore.setState({file: undefined, operation: 'none'}); - }}> - <div className={"project-breadcrumb"}>{project?.name + " (" + project?.projectId + ")"}</div> - </BreadcrumbItem> - </Breadcrumb> - </FlexItem> - <FlexItem> - <Flex direction={{default: "row"}}> - <FlexItem> - <Badge>{getProjectFileType(file)}</Badge> - </FlexItem> - <FlexItem> - <TextContent className="description"> - <Text>{isLog ? filename : file.name}</Text> - </TextContent> - </FlexItem> + + function getProjectTitle() { + return ( + <Flex direction={{default: "column"}}> + <FlexItem> + <TextContent className="title"> + <Text component="h2">{project?.name + " (" + project?.projectId + ")"}</Text> + </TextContent> + </FlexItem> + <FlexItem> + <TextContent className="description"> + <Text>{project?.description}</Text> + </TextContent> + </FlexItem> + </Flex> + ) + } + + function getFileTitle() { + return (isFile ? + <Flex alignItems={{default: "alignItemsCenter"}}> + <Flex direction={{default: "column"}}> + <FlexItem> + <Breadcrumb> + <BreadcrumbItem to="#" onClick={event => { + setFile('none', undefined); + }}> + <div className={"project-breadcrumb"}>{'Back to ' +project?.name + " project"}</div> + </BreadcrumbItem> + <BreadcrumbItem to="#" onClick={_ => { + setTabIndex('topology'); + setFile('none', undefined); + }}> + <TopologyIcon/> + </BreadcrumbItem> + <BreadcrumbItem to="#files" onClick={_ => { + setTabIndex('files'); + setFile('none', undefined); + }}> + <FilesIcon/> + </BreadcrumbItem> + </Breadcrumb> + </FlexItem> + <FlexItem> + <Flex direction={{default: "row"}}> + <FlexItem> + <Badge>{getProjectFileType(file)}</Badge> + </FlexItem> + <FlexItem> + <TextContent className="description"> + <Text>{isLog ? filename : file.name}</Text> + </TextContent> + </FlexItem> + </Flex> + </FlexItem> + </Flex> </Flex> - </FlexItem> - </Flex>} - {!isFile && <Flex direction={{default: "column"}} > - <FlexItem> - <TextContent className="title"> - <Text component="h2">{project?.name + " (" + project?.projectId + ")"}</Text> - </TextContent> - </FlexItem> - <FlexItem> - <TextContent className="description"> - <Text>{project?.description}</Text> - </TextContent> - </FlexItem> - </Flex>} - </div>) + : <></> + ) + } + + return ( + <div className="dsl-title project-title"> + {isFile && getFileTitle()} + {!isFile && getProjectTitle()} + </div> + ) } diff --git a/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx b/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx index a3c9471a..ef5f9ba5 100644 --- a/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx +++ b/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx @@ -27,6 +27,7 @@ import {DevModeToolbar} from "./DevModeToolbar"; import {useFileStore, useProjectStore} from "../api/ProjectStore"; import {shallow} from "zustand/shallow"; import {BuildToolbar} from "./BuildToolbar"; +import {EditorToolbar} from "../editor/EditorToolbar"; export function ProjectToolbar() { @@ -40,23 +41,6 @@ export function ProjectToolbar() { return file !== undefined; } - function getFileToolbar() { - return ( - <Toolbar id="toolbar-group-types"> - <ToolbarContent> - <Flex className="" direction={{default: "row"}} - justifyContent={{default: 'justifyContentSpaceBetween'}} - alignItems={{default: "alignItemsCenter"}}> - {isRunnable() && - <FlexItem align={{default: 'alignRight'}}> - <DevModeToolbar reloadOnly={true}/> - </FlexItem> - } - </Flex> - </ToolbarContent> - </Toolbar> - ) - } function getProjectToolbar() { return (<Toolbar id="toolbar-group-types"> @@ -87,5 +71,5 @@ export function ProjectToolbar() { return !isKameletsProject() && !isTemplatesProject() && !isServicesProject() && ['build', 'container'].includes(tabIndex.toString()); } - return isFile() ? getFileToolbar() : getProjectToolbar() + return isFile() ? <EditorToolbar/> : getProjectToolbar() }