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 5979387c2f2a4db5523487f0094e9e53773c9440 Author: Marat Gubaidullin <ma...@talismancloud.io> AuthorDate: Wed Sep 13 14:54:55 2023 -0400 UI Consistency for #885 --- karavan-designer/package-lock.json | 16 +++--- karavan-designer/package.json | 4 +- karavan-designer/src/App.tsx | 10 ++-- karavan-designer/src/DesignerPage.tsx | 44 ++++----------- karavan-designer/src/designer/KaravanDesigner.tsx | 42 +++++++++------ karavan-designer/src/designer/KaravanStore.ts | 8 +++ .../src/designer/editor/CodeEditor.tsx | 63 ++++++++++++++++++++++ karavan-designer/src/designer/karavan.css | 9 ++-- karavan-designer/src/designer/utils/EventBus.ts | 2 +- .../src/designer/utils/KaravanIcons.tsx | 15 ++++++ .../src/designer/utils/Notification.tsx | 2 +- 11 files changed, 142 insertions(+), 73 deletions(-) diff --git a/karavan-designer/package-lock.json b/karavan-designer/package-lock.json index cd1d4ad6..96126a8c 100644 --- a/karavan-designer/package-lock.json +++ b/karavan-designer/package-lock.json @@ -9,7 +9,7 @@ "version": "4.0.0-RC2", "license": "Apache-2.0", "dependencies": { - "@monaco-editor/react": "4.5.1", + "@monaco-editor/react": "^4.5.2", "@patternfly/patternfly": "^5.0.2", "@patternfly/react-core": "^5.0.0", "@patternfly/react-table": "^5.0.0", @@ -35,7 +35,7 @@ "@typescript-eslint/eslint-plugin": "^5.59.2", "@typescript-eslint/parser": "^5.59.2", "eslint": "^8.39.0", - "monaco-editor": "0.41.0", + "monaco-editor": "0.43.0", "react-scripts": "5.0.1", "typescript": "^4.9.5" } @@ -3361,9 +3361,9 @@ } }, "node_modules/@monaco-editor/react": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.5.1.tgz", - "integrity": "sha512-NNDFdP+2HojtNhCkRfE6/D6ro6pBNihaOzMbGK84lNWzRu+CfBjwzGt4jmnqimLuqp5yE5viHS2vi+QOAnD5FQ==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.5.2.tgz", + "integrity": "sha512-emcWu6vg1OpXPiYll4aPOaXe8bwYB4UaaNTwtArFLgMoNGBzRZb2Xn0Bra2HMIFM7QLgs7fCGunHO5LkfT2LBA==", "dependencies": { "@monaco-editor/loader": "^1.3.3" }, @@ -13040,9 +13040,9 @@ } }, "node_modules/monaco-editor": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.41.0.tgz", - "integrity": "sha512-1o4olnZJsiLmv5pwLEAmzHTE/5geLKQ07BrGxlF4Ri/AXAc2yyDGZwHjiTqD8D/ROKUZmwMA28A+yEowLNOEcA==" + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.43.0.tgz", + "integrity": "sha512-cnoqwQi/9fml2Szamv1XbSJieGJ1Dc8tENVMD26Kcfl7xGQWp7OBKMjlwKVGYFJ3/AXJjSOGvcqK7Ry/j9BM1Q==" }, "node_modules/ms": { "version": "2.1.2", diff --git a/karavan-designer/package.json b/karavan-designer/package.json index 1450bd44..92534f5b 100644 --- a/karavan-designer/package.json +++ b/karavan-designer/package.json @@ -26,7 +26,7 @@ ] }, "dependencies": { - "@monaco-editor/react": "4.5.1", + "@monaco-editor/react": "^4.5.2", "@patternfly/patternfly": "^5.0.2", "@patternfly/react-core": "^5.0.0", "@patternfly/react-table": "^5.0.0", @@ -52,7 +52,7 @@ "@typescript-eslint/eslint-plugin": "^5.59.2", "@typescript-eslint/parser": "^5.59.2", "eslint": "^8.39.0", - "monaco-editor": "0.41.0", + "monaco-editor": "0.43.0", "react-scripts": "5.0.1", "typescript": "^4.9.5" }, diff --git a/karavan-designer/src/App.tsx b/karavan-designer/src/App.tsx index 0874bb06..b71267ba 100644 --- a/karavan-designer/src/App.tsx +++ b/karavan-designer/src/App.tsx @@ -16,10 +16,8 @@ */ import * as React from "react"; import { - Alert, - AlertActionCloseButton, AlertGroup, - Bullseye, Button, Divider, Flex, FlexItem, Masthead, MastheadBrand, MastheadContent, MastheadMain, MastheadToggle, - Page, PageSidebar, PageSidebarBody, PageToggleButton, Spinner, Tooltip, + Bullseye, Button, Divider, Flex, FlexItem, Masthead, + Page, PageSidebar, PageSidebarBody, Spinner, Tooltip, } from "@patternfly/react-core"; import {KameletApi} from "karavan-core/lib/api/KameletApi"; import {ComponentApi} from "karavan-core/lib/api/ComponentApi"; @@ -31,7 +29,7 @@ import {DesignerPage} from "./DesignerPage"; import {TemplateApi} from "karavan-core/lib/api/TemplateApi"; import {KnowledgebasePage} from "./knowledgebase/KnowledgebasePage"; import {Notification} from "./designer/utils/Notification"; -import {EventBus, ToastMessage} from "./designer/utils/EventBus"; +import {EventBus} from "./designer/utils/EventBus"; class MenuItem { pageId: string = ''; @@ -103,7 +101,7 @@ class App extends React.Component<Props, State> { } save(filename: string, yaml: string, propertyOnly: boolean) { - console.log(yaml); + // console.log(yaml); } getSpinner() { diff --git a/karavan-designer/src/DesignerPage.tsx b/karavan-designer/src/DesignerPage.tsx index 4447a308..909b15c5 100644 --- a/karavan-designer/src/DesignerPage.tsx +++ b/karavan-designer/src/DesignerPage.tsx @@ -19,15 +19,14 @@ import { Toolbar, ToolbarContent, ToolbarItem, - PageSection, TextContent, Text, Flex, FlexItem, Button, Tooltip, ToggleGroup, ToggleGroupItem + PageSection, TextContent, Text, Flex, FlexItem, Button, Tooltip, ToggleGroup, ToggleGroupItem, Page } from '@patternfly/react-core'; import './designer/karavan.css'; import DownloadIcon from "@patternfly/react-icons/dist/esm/icons/download-icon"; import DownloadImageIcon from "@patternfly/react-icons/dist/esm/icons/image-icon"; import {KaravanDesigner} from "./designer/KaravanDesigner"; import Editor from "@monaco-editor/react"; -import {EventBus, IntegrationUpdate} from "./designer/utils/EventBus"; -import {InfrastructureAPI} from "./designer/utils/InfrastructureAPI"; +import {EventBus} from "./designer/utils/EventBus"; interface Props { name: string, @@ -38,7 +37,6 @@ interface Props { export const DesignerPage = (props: Props) => { - const [mode, setMode] = useState<"design" | "code">('design'); const [yaml, setYaml] = useState<string>(props.yaml); useEffect(() => { @@ -51,7 +49,7 @@ export const DesignerPage = (props: Props) => { props.onSave(filename, yaml, propertyOnly); } - function download () { + function download() { const {name, yaml} = props; if (name && yaml) { const a = document.createElement('a'); @@ -61,15 +59,16 @@ export const DesignerPage = (props: Props) => { } } - function downloadImage () { + function downloadImage() { EventBus.sendCommand("downloadImage"); } - function getDesigner () { + function getDesigner() { return ( <KaravanDesigner dark={props.dark} filename={props.name} + showCodeTab={true} yaml={yaml} onSave={(filename, yaml, propertyOnly) => save(filename, yaml, propertyOnly)} onGetCustomCode={name => { @@ -82,23 +81,6 @@ export const DesignerPage = (props: Props) => { ) } - function getEditor () { - return ( - <Editor - height="100vh" - defaultLanguage="yaml" - theme={'light'} - value={yaml} - className={'code-editor'} - onChange={(value, ev) => { - if (value) { - save(props.name, value, false) - } - }} - /> - ) - } - return ( <PageSection className="designer-page" padding={{default: 'noPadding'}}> <div className="tools-section" //padding={{default: 'noPadding'}} @@ -112,14 +94,6 @@ export const DesignerPage = (props: Props) => { <FlexItem> <Toolbar id="toolbar-group-types"> <ToolbarContent> - <ToolbarItem> - <ToggleGroup> - <ToggleGroupItem text="Design" buttonId="design" isSelected={mode === "design"} - onChange={(_event, s) => setMode("design")} /> - <ToggleGroupItem text="Code" buttonId="code" isSelected={mode === "code"} - onChange={(_event, s) => setMode("code")} /> - </ToggleGroup> - </ToolbarItem> <ToolbarItem> <Tooltip content="Download YAML" position={"bottom"}> <Button variant="primary" icon={<DownloadIcon/>} onClick={e => download()}> @@ -129,7 +103,8 @@ export const DesignerPage = (props: Props) => { </ToolbarItem> <ToolbarItem> <Tooltip content="Download image" position={"bottom"}> - <Button variant="secondary" icon={<DownloadImageIcon/>} onClick={e => downloadImage()}> + <Button variant="secondary" icon={<DownloadImageIcon/>} + onClick={e => downloadImage()}> Image </Button> </Tooltip> @@ -139,8 +114,7 @@ export const DesignerPage = (props: Props) => { </FlexItem> </Flex> </div> - {mode === 'design' && getDesigner()} - {mode === 'code' && getEditor()} + {getDesigner()} </PageSection> ) }; \ No newline at end of file diff --git a/karavan-designer/src/designer/KaravanDesigner.tsx b/karavan-designer/src/designer/KaravanDesigner.tsx index badf8ba8..cb58a377 100644 --- a/karavan-designer/src/designer/KaravanDesigner.tsx +++ b/karavan-designer/src/designer/KaravanDesigner.tsx @@ -17,13 +17,15 @@ import React, {useEffect, useState} from 'react'; import { Badge, + Button, PageSection, PageSectionVariants, - Switch, Tab, Tabs, - TabTitleIcon, TabTitleText, + TabTitleIcon, + TabTitleText, Tooltip, + TooltipPosition, } from '@patternfly/react-core'; import './karavan.css'; import {RouteDesigner} from "./route/RouteDesigner"; @@ -35,9 +37,11 @@ import {useDesignerStore, useIntegrationStore} from "./KaravanStore"; import {shallow} from "zustand/shallow"; import {getDesignerIcon} from "./utils/KaravanIcons"; import {InfrastructureAPI} from "./utils/InfrastructureAPI"; -import {EventBus, IntegrationUpdate, ToastMessage} from "./utils/EventBus"; +import {EventBus, IntegrationUpdate} from "./utils/EventBus"; import {RestDesigner} from "./rest/RestDesigner"; import {BeansDesigner} from "./beans/BeansDesigner"; +import {CodeEditor} from "./editor/CodeEditor"; +import BellIcon from '@patternfly/react-icons/dist/esm/icons/bell-icon'; interface Props { onSave: (filename: string, yaml: string, propertyOnly: boolean) => void @@ -48,13 +52,14 @@ interface Props { dark: boolean hideLogDSL?: boolean tab?: string + showCodeTab: boolean } -export function KaravanDesigner (props: Props) { +export function KaravanDesigner(props: Props) { const [tab, setTab] = useState<string>('routes'); - const [setDark, hideLogDSL, setHideLogDSL, setSelectedStep, reset] = useDesignerStore((s) => - [s.setDark, s.hideLogDSL, s.setHideLogDSL, s.setSelectedStep, s.reset], shallow) + const [setDark, hideLogDSL, setHideLogDSL, setSelectedStep, reset, badge, message] = useDesignerStore((s) => + [s.setDark, s.hideLogDSL, s.setHideLogDSL, s.setSelectedStep, s.reset, s.notificationBadge, s.notificationMessage], shallow) const [integration, setIntegration] = useIntegrationStore((s) => [s.integration, s.setIntegration], shallow) @@ -101,20 +106,23 @@ export function KaravanDesigner (props: Props) { return CamelDefinitionYaml.integrationToYaml(clone); } - function getTab(title: string, tooltip: string, icon: string) { + function getTab(title: string, tooltip: string, icon: string, showBadge: boolean = false) { const counts = CamelUi.getFlowCounts(integration); const count = counts.has(icon) && counts.get(icon) ? counts.get(icon) : undefined; const showCount = count && count > 0; + const color= showBadge && badge ? "red" : "initial"; return ( - <Tooltip position={"bottom"} - content={<div>{tooltip}</div>}> - <div className="top-menu-item"> - <TabTitleIcon>{getDesignerIcon(icon)}</TabTitleIcon> - <TabTitleText>{title}</TabTitleText> - {showCount && <Badge isRead className="count">{counts.get(icon)}</Badge>} - </div> - </Tooltip> - + <div className="top-menu-item" style={{color: color}}> + <TabTitleIcon>{getDesignerIcon(icon)}</TabTitleIcon> + <TabTitleText>{title}</TabTitleText> + {showCount && <Badge isRead className="count">{counts.get(icon)}</Badge>} + {showBadge && badge && + <Button variant="link" + icon={<BellIcon color="red"/>} + style={{visibility: (badge ? 'visible' : 'hidden'), padding: '0', margin: '0'}} + onClick={event => EventBus.sendAlert(message[0], message[1], 'danger')}/> + } + </div> ) } @@ -132,6 +140,7 @@ export function KaravanDesigner (props: Props) { <Tab eventKey='routes' title={getTab("Routes", "Integration flows", "routes")}></Tab> <Tab eventKey='rest' title={getTab("REST", "REST services", "rest")}></Tab> <Tab eventKey='beans' title={getTab("Beans", "Beans Configuration", "beans")}></Tab> + {props.showCodeTab && <Tab eventKey='code' title={getTab("YAML", "YAML Code", "code", true)}></Tab>} </Tabs> {/*{tab === 'routes' && <Tooltip content={"Hide Log elements"}>*/} {/* <Switch*/} @@ -150,6 +159,7 @@ export function KaravanDesigner (props: Props) { {tab === 'routes' && <RouteDesigner/>} {tab === 'rest' && <RestDesigner/>} {tab === 'beans' && <BeansDesigner/>} + {tab === 'code' && <CodeEditor/>} </PageSection> ) } \ No newline at end of file diff --git a/karavan-designer/src/designer/KaravanStore.ts b/karavan-designer/src/designer/KaravanStore.ts index e524e625..15a9b7be 100644 --- a/karavan-designer/src/designer/KaravanStore.ts +++ b/karavan-designer/src/designer/KaravanStore.ts @@ -152,6 +152,8 @@ export const useConnectionsStore = createWithEqualityFn<ConnectionsState>((set) type DesignerState = { dark: boolean; + notificationBadge: boolean; + notificationMessage: [string, string]; hideLogDSL: boolean; shiftKeyPressed: boolean; showDeleteConfirmation: boolean; @@ -166,6 +168,8 @@ type DesignerState = { left: number, } const designerState: DesignerState = { + notificationBadge: false, + notificationMessage: ['', ''], dark: false, hideLogDSL: false, shiftKeyPressed: false, @@ -192,6 +196,7 @@ type DesignerAction = { setClipboardSteps: (clipboardSteps: CamelElement[]) => void; setPosition: (width: number, height: number, top: number, left: number) => void; reset: () => void; + setNotification: (notificationBadge: boolean, notificationMessage: [string, string]) => void; } export const useDesignerStore = createWithEqualityFn<DesignerState & DesignerAction>((set) => ({ @@ -240,5 +245,8 @@ export const useDesignerStore = createWithEqualityFn<DesignerState & DesignerAct }, reset: () => { set(designerState); + }, + setNotification: (notificationBadge: boolean, notificationMessage: [string, string]) => { + set({notificationBadge: notificationBadge, notificationMessage: notificationMessage}) } }), shallow) \ No newline at end of file diff --git a/karavan-designer/src/designer/editor/CodeEditor.tsx b/karavan-designer/src/designer/editor/CodeEditor.tsx index e69de29b..323bf0cf 100644 --- a/karavan-designer/src/designer/editor/CodeEditor.tsx +++ b/karavan-designer/src/designer/editor/CodeEditor.tsx @@ -0,0 +1,63 @@ +/* + * 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 {shallow} from "zustand/shallow"; +import {useDesignerStore, useIntegrationStore} from "../KaravanStore"; +import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml"; +import {EventBus} from "../utils/EventBus"; + +export function CodeEditor () { + + const [integration, setIntegration] = useIntegrationStore((s) => [s.integration, s.setIntegration], shallow); + const [setNotification, badge] = useDesignerStore((s) => [s.setNotification, s.notificationBadge], shallow) + const [code, setCode] = useState<string>(''); + + useEffect(() => { + const c = CamelDefinitionYaml.integrationToYaml(integration); + setCode(c); + return () => { + setNotification(false, ['', '']); + } + }, []); + + function onChange(value: string | undefined) { + if (value) { + try { + const i = CamelDefinitionYaml.yamlToIntegration(integration.metadata.name, value); + setIntegration(i, false); + setNotification(false, ['', '']); + } catch (e: any) { + const message: string = e?.message ? e.message : e.reason; + setNotification(true, ['Error in YAML, Integration can not be saved!' ,message]); + } + } + } + + return ( + <Editor + height="100vh" + defaultLanguage={'yaml'} + theme={'light'} + value={code} + className={'code-editor'} + defaultValue={code} + onChange={(value, ev) => onChange(value)} + /> + ) +} diff --git a/karavan-designer/src/designer/karavan.css b/karavan-designer/src/designer/karavan.css index bf4d9d49..f1fd43b4 100644 --- a/karavan-designer/src/designer/karavan.css +++ b/karavan-designer/src/designer/karavan.css @@ -1046,8 +1046,9 @@ /*REST Page*/ .karavan .rest-page { - flex: 1; - overflow: auto; + /*flex: 1;*/ + /*overflow: auto;*/ + /*height: 100%;*/ } .karavan .rest-page .rest-page-columns { @@ -1066,7 +1067,7 @@ } .karavan .rest-page .flows { - width: 800px; + /*width: 800px;*/ margin: 0 auto 80px auto; } @@ -1167,7 +1168,7 @@ .karavan .rest-page .rest-config-card .description, .karavan .rest-page .method-card .description { margin: auto 0 auto 0; - width: 670px; + min-width: 200px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; diff --git a/karavan-designer/src/designer/utils/EventBus.ts b/karavan-designer/src/designer/utils/EventBus.ts index 8f3f9712..0a20d3c6 100644 --- a/karavan-designer/src/designer/utils/EventBus.ts +++ b/karavan-designer/src/designer/utils/EventBus.ts @@ -76,7 +76,7 @@ export class ToastMessage { id: string = '' text: string = '' title: string = '' - variant?: 'success' | 'danger' | 'warning' | 'info' | 'custom'; + variant: 'success' | 'danger' | 'warning' | 'info' | 'custom'; constructor(title: string, text: string, variant: 'success' | 'danger' | 'warning' | 'info' | 'custom') { this.id = uuidv4(); diff --git a/karavan-designer/src/designer/utils/KaravanIcons.tsx b/karavan-designer/src/designer/utils/KaravanIcons.tsx index 17560ff4..9e514046 100644 --- a/karavan-designer/src/designer/utils/KaravanIcons.tsx +++ b/karavan-designer/src/designer/utils/KaravanIcons.tsx @@ -263,6 +263,21 @@ export function CamelIcon(props?: (JSX.IntrinsicAttributes & React.SVGProps<SVGS } export function getDesignerIcon(icon: string) { + if (icon === 'code') return ( + <svg + className="top-icon" id="icon" + xmlns="http://www.w3.org/2000/svg" + width="24" + height="24" + fill="none" + viewBox="0 0 24 24" + > + <path + fill="#000000" + d="M8.502 5.387a.75.75 0 00-1.004-1.115L5.761 5.836c-.737.663-1.347 1.212-1.767 1.71-.44.525-.754 1.088-.754 1.784 0 .695.313 1.258.754 1.782.42.499 1.03 1.049 1.767 1.711l1.737 1.564a.75.75 0 101.004-1.115l-1.697-1.527c-.788-.709-1.319-1.19-1.663-1.598-.33-.393-.402-.622-.402-.817 0-.196.072-.425.402-.818.344-.409.875-.889 1.663-1.598l1.697-1.527zM14.18 4.275a.75.75 0 01.532.918l-3.987 15a.75.75 0 11-1.45-.386l3.987-15a.75.75 0 01.918-.532zM15.443 10.498a.75.75 0 011.059-.05 [...] + ></path> + </svg> + ) if (icon === 'routes') return ( <svg className="top-icon" width="32px" height="32px" viewBox="0 0 32 32" id="icon"> <defs> diff --git a/karavan-designer/src/designer/utils/Notification.tsx b/karavan-designer/src/designer/utils/Notification.tsx index f3572ff4..520062c8 100644 --- a/karavan-designer/src/designer/utils/Notification.tsx +++ b/karavan-designer/src/designer/utils/Notification.tsx @@ -28,7 +28,7 @@ export function Notification () { <AlertGroup isToast isLiveRegion> {alerts.map((e: ToastMessage) => ( <Alert key={e.id} className="main-alert" variant={e.variant} title={e.title} - timeout={e.variant === "success" ? 1000 : 2000} + timeout={['success', 'info', 'custom'].includes(e.variant) ? 1000 : 20000} actionClose={<AlertActionCloseButton onClose={() => { setAlerts(prevState => { return [...prevState.filter(t => t.id !== e.id)];