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 d58055f60b23cb5f148eced2b8683b2bae50b48e Author: Marat Gubaidullin <ma...@talismancloud.io> AuthorDate: Wed Sep 13 15:39:12 2023 -0400 UI Consistency for #885 --- karavan-space/src/designer/KaravanDesigner.tsx | 42 +++++++++------ karavan-space/src/designer/KaravanStore.ts | 8 +++ karavan-space/src/designer/MainToolbar.tsx | 12 +++-- karavan-space/src/designer/editor/CodeEditor.tsx | 63 ++++++++++++++++++++++ karavan-space/src/designer/karavan.css | 9 ++-- karavan-space/src/designer/utils/EventBus.ts | 2 +- karavan-space/src/designer/utils/KaravanIcons.tsx | 15 ++++++ karavan-space/src/designer/utils/Notification.tsx | 2 +- karavan-space/src/space/SpacePage.tsx | 35 ++---------- .../camel/karavan/infinispan/DataGridTest.java | 41 -------------- 10 files changed, 130 insertions(+), 99 deletions(-) diff --git a/karavan-space/src/designer/KaravanDesigner.tsx b/karavan-space/src/designer/KaravanDesigner.tsx index badf8ba8..cb58a377 100644 --- a/karavan-space/src/designer/KaravanDesigner.tsx +++ b/karavan-space/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-space/src/designer/KaravanStore.ts b/karavan-space/src/designer/KaravanStore.ts index e524e625..15a9b7be 100644 --- a/karavan-space/src/designer/KaravanStore.ts +++ b/karavan-space/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-space/src/designer/MainToolbar.tsx b/karavan-space/src/designer/MainToolbar.tsx index 8923b70c..fd86e9d6 100644 --- a/karavan-space/src/designer/MainToolbar.tsx +++ b/karavan-space/src/designer/MainToolbar.tsx @@ -6,6 +6,7 @@ import '../designer/karavan.css'; interface Props { title: React.ReactNode; + toolsStart?: React.ReactNode; tools: React.ReactNode; } @@ -13,15 +14,18 @@ export function MainToolbar(props: Props) { return ( <PageSection className="tools-section" variant={PageSectionVariants.light}> - <Flex className="tools" justifyContent={{default: 'justifyContentSpaceBetween'}} + <Flex className="tools" justifyContent={{default: 'justifyContentFlexStart'}} alignItems={{default: 'alignItemsCenter'}}> - <FlexItem> + <FlexItem flex={{default: "flexNone"}}> {props.title} </FlexItem> - <FlexItem> + <FlexItem align={{default: 'alignLeft'}}> + {props.toolsStart} + </FlexItem> + <FlexItem align={{default: 'alignRight'}}> {props.tools} </FlexItem> </Flex> </PageSection> - ); + ) } diff --git a/karavan-space/src/designer/editor/CodeEditor.tsx b/karavan-space/src/designer/editor/CodeEditor.tsx new file mode 100644 index 00000000..323bf0cf --- /dev/null +++ b/karavan-space/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-space/src/designer/karavan.css b/karavan-space/src/designer/karavan.css index bf4d9d49..f1fd43b4 100644 --- a/karavan-space/src/designer/karavan.css +++ b/karavan-space/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-space/src/designer/utils/EventBus.ts b/karavan-space/src/designer/utils/EventBus.ts index 8f3f9712..0a20d3c6 100644 --- a/karavan-space/src/designer/utils/EventBus.ts +++ b/karavan-space/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-space/src/designer/utils/KaravanIcons.tsx b/karavan-space/src/designer/utils/KaravanIcons.tsx index 17560ff4..9e514046 100644 --- a/karavan-space/src/designer/utils/KaravanIcons.tsx +++ b/karavan-space/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-space/src/designer/utils/Notification.tsx b/karavan-space/src/designer/utils/Notification.tsx index f3572ff4..520062c8 100644 --- a/karavan-space/src/designer/utils/Notification.tsx +++ b/karavan-space/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)]; diff --git a/karavan-space/src/space/SpacePage.tsx b/karavan-space/src/space/SpacePage.tsx index 2916aff1..895955ad 100644 --- a/karavan-space/src/space/SpacePage.tsx +++ b/karavan-space/src/space/SpacePage.tsx @@ -44,7 +44,6 @@ interface State { key: string, karavanDesignerRef: any, showUploadModal: boolean, - mode: "design" | "code", } export class SpacePage extends React.Component<Props, State> { @@ -52,7 +51,6 @@ export class SpacePage extends React.Component<Props, State> { public state: State = { key: Math.random().toString(), karavanDesignerRef: React.createRef(), - mode: "design", showUploadModal: false } @@ -97,6 +95,7 @@ export class SpacePage extends React.Component<Props, State> { const {name, yaml} = this.props; return ( <KaravanDesigner + showCodeTab={true} key={this.state.key} dark={this.props.dark} // ref={this.state.karavanDesignerRef} @@ -113,27 +112,8 @@ export class SpacePage extends React.Component<Props, State> { ) } - getEditor = () => { - const {name, yaml} = this.props; - return ( - <Editor - height="100vh" - defaultLanguage="yaml" - theme={'light'} - value={yaml} - className={'code-editor'} - onChange={(value, ev) => { - if (value) { - this.save(name, value, false) - } - }} - /> - ) - } - - render() { - const {mode, showUploadModal} = this.state; + const {showUploadModal} = this.state; return ( <PageSection className="kamelet-section designer-page" padding={{default: 'noPadding'}}> <PageSection className="tools-section" padding={{default: 'noPadding'}} @@ -146,14 +126,6 @@ export class SpacePage extends React.Component<Props, State> { <Text component="h2">Integration</Text> </TextContent> </FlexItem> - <FlexItem> - <ToggleGroup> - <ToggleGroupItem text="Design" buttonId="design" isSelected={mode === "design"} - onChange={(_event, s) => this.setState({mode: 'design'})} /> - <ToggleGroupItem text="Code" buttonId="code" isSelected={mode === "code"} - onChange={(_event, s) => this.setState({mode: 'code'})} /> - </ToggleGroup> - </FlexItem> </Flex> </FlexItem> <FlexItem> @@ -199,8 +171,7 @@ export class SpacePage extends React.Component<Props, State> { </FlexItem> </Flex> </PageSection> - {mode === 'design' && this.getDesigner()} - {mode === 'code' && this.getEditor()} + {this.getDesigner()} <UploadModal isOpen={showUploadModal} onClose={yaml => this.addYaml(yaml)}/> </PageSection> ); diff --git a/karavan-web/karavan-app/src/test/java/org/apache/camel/karavan/infinispan/DataGridTest.java b/karavan-web/karavan-app/src/test/java/org/apache/camel/karavan/infinispan/DataGridTest.java deleted file mode 100644 index 34d8ee93..00000000 --- a/karavan-web/karavan-app/src/test/java/org/apache/camel/karavan/infinispan/DataGridTest.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.apache.camel.karavan.infinispan; - -import io.quarkus.test.junit.QuarkusTest; -import jakarta.inject.Inject; -import org.apache.camel.karavan.infinispan.InfinispanService; -import org.apache.camel.karavan.infinispan.model.CamelStatus; -import org.apache.camel.karavan.infinispan.model.ProjectFile; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; - -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -@QuarkusTest -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -public class DataGridTest { - - @Inject - InfinispanService infinispanService; - - @BeforeAll - public void setup() throws Exception { - infinispanService.start(); - } - - @Test - public void testProjectFiles() throws InterruptedException { - List<ProjectFile> files = infinispanService.getProjectFiles("xxx"); - assertEquals(0, files.size()); - } - - @Test - public void testCamelStatuses() throws InterruptedException { -// CamelStatus cs = new CamelStatus("test1", "container1", CamelStatus.Name.context, "", "dev"); -// infinispanService.saveCamelStatus(cs); -// List<CamelStatus> list = infinispanService.getCamelStatusesByEnv("dev", CamelStatus.Name.context); -// assertEquals(1, list.size()); - } -}