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 86a7de952c4eadc983bdaf94d6d5571ec4f86903 Author: Marat Gubaidullin <ma...@talismancloud.io> AuthorDate: Wed Sep 13 14:54:46 2023 -0400 UI Consistency for #885 --- .../src/designer/editor/CodeEditor.tsx | 0 .../src/main/webui/src/api/ProjectStore.ts | 6 -- .../main/webui/src/designer/KaravanDesigner.tsx | 42 +++++++---- .../src/main/webui/src/designer/KaravanStore.ts | 8 ++ .../main/webui/src/designer/editor/CodeEditor.tsx | 63 ++++++++++++++++ .../src/main/webui/src/designer/karavan.css | 9 ++- .../src/main/webui/src/designer/utils/EventBus.ts | 2 +- .../main/webui/src/designer/utils/KaravanIcons.tsx | 15 ++++ .../main/webui/src/designer/utils/Notification.tsx | 2 +- .../src/main/webui/src/project/ProjectToolbar.tsx | 35 +-------- .../src/main/webui/src/project/file/FileEditor.tsx | 13 ++-- .../webui/src/project/file/PropertiesPanel.tsx | 87 ++++++++++++++++++++++ .../webui/src/project/file/PropertiesTable.tsx | 60 +++++++-------- .../webui/src/project/file/PropertiesToolbar.tsx | 63 ++++++++++++++++ 14 files changed, 310 insertions(+), 95 deletions(-) diff --git a/karavan-designer/src/designer/editor/CodeEditor.tsx b/karavan-designer/src/designer/editor/CodeEditor.tsx new file mode 100644 index 00000000..e69de29b diff --git a/karavan-web/karavan-app/src/main/webui/src/api/ProjectStore.ts b/karavan-web/karavan-app/src/main/webui/src/api/ProjectStore.ts index 8e14aa29..1f2d6391 100644 --- a/karavan-web/karavan-app/src/main/webui/src/api/ProjectStore.ts +++ b/karavan-web/karavan-app/src/main/webui/src/api/ProjectStore.ts @@ -182,8 +182,6 @@ interface FileState { setEditAdvancedProperties: (editAdvancedProperties: boolean) => void; addProperty: string; setAddProperty: (addProperty: string) => void; - mode: "design" | "code", - setMode: (mode: "design" | "code") => void; } export const useFileStore = createWithEqualityFn<FileState>((set) => ({ @@ -191,10 +189,6 @@ export const useFileStore = createWithEqualityFn<FileState>((set) => ({ operation: "none", editAdvancedProperties: false, addProperty: '', - mode: "design", - setMode: (mode: "design" | "code") => { - set(() => ({mode: mode})); - }, setFile: (operation: "create" | "select" | "delete"| "none" | "copy" | "upload", file?: ProjectFile) => { set((state: FileState) => ({ file: file, diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx index badf8ba8..cb58a377 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx +++ b/karavan-web/karavan-app/src/main/webui/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-web/karavan-app/src/main/webui/src/designer/KaravanStore.ts b/karavan-web/karavan-app/src/main/webui/src/designer/KaravanStore.ts index e524e625..15a9b7be 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/KaravanStore.ts +++ b/karavan-web/karavan-app/src/main/webui/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-web/karavan-app/src/main/webui/src/designer/editor/CodeEditor.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/editor/CodeEditor.tsx new file mode 100644 index 00000000..323bf0cf --- /dev/null +++ b/karavan-web/karavan-app/src/main/webui/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-web/karavan-app/src/main/webui/src/designer/karavan.css b/karavan-web/karavan-app/src/main/webui/src/designer/karavan.css index bf4d9d49..f1fd43b4 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/karavan.css +++ b/karavan-web/karavan-app/src/main/webui/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-web/karavan-app/src/main/webui/src/designer/utils/EventBus.ts b/karavan-web/karavan-app/src/main/webui/src/designer/utils/EventBus.ts index 8f3f9712..0a20d3c6 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/utils/EventBus.ts +++ b/karavan-web/karavan-app/src/main/webui/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-web/karavan-app/src/main/webui/src/designer/utils/KaravanIcons.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/utils/KaravanIcons.tsx index 17560ff4..9e514046 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/utils/KaravanIcons.tsx +++ b/karavan-web/karavan-app/src/main/webui/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-web/karavan-app/src/main/webui/src/designer/utils/Notification.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/utils/Notification.tsx index f3572ff4..520062c8 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/utils/Notification.tsx +++ b/karavan-web/karavan-app/src/main/webui/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-web/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx b/karavan-web/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx index 84f0cd2c..7a14f166 100644 --- a/karavan-web/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx @@ -27,9 +27,9 @@ import {BuildToolbar} from "./BuildToolbar"; export function ProjectToolbar () { const [project, isPushing, tabIndex] = useProjectStore((s) => [s.project, s.isPushing, s.tabIndex], shallow ) - const [file, editAdvancedProperties, setEditAdvancedProperties, setAddProperty, mode, setMode] + const [file, editAdvancedProperties, setEditAdvancedProperties, setAddProperty] = useFileStore((state) => - [state.file, state.editAdvancedProperties, state.setEditAdvancedProperties, state.setAddProperty, state.mode, state.setMode], shallow ) + [state.file, state.editAdvancedProperties, state.setEditAdvancedProperties, state.setAddProperty], shallow ) useEffect(() => { }, [project, file]); @@ -58,42 +58,13 @@ export function ProjectToolbar () { EventBus.sendCommand("downloadImage"); } - function addProperty() { - if (file) { - const project = file ? ProjectModelApi.propertiesToProject(file?.code) : ProjectModel.createNew(); - const props = project.properties; - props.push(ProjectProperty.createNew("", "")); - file.code = ProjectModelApi.propertiesToString(props); - ProjectService.saveFile(file, true); - setAddProperty(Math.random().toString()); - } - } + function getFileToolbar() { return <Toolbar id="toolbar-group-types"> <ToolbarContent> <Flex className="toolbar" direction={{default: "row"}} alignItems={{default: "alignItemsCenter"}}> {isRunnable() && <DevModeToolbar reloadOnly={true}/>} - {isIntegration() && <FlexItem> - <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> - </FlexItem>} - - {isProperties() && <FlexItem> - <Checkbox - id="advanced" - label="Edit advanced" - isChecked={editAdvancedProperties} - onChange={(_, checked) => setEditAdvancedProperties(checked)} - /> - </FlexItem>} - {isProperties() && <FlexItem> - <Button size="sm" variant="primary" icon={<PlusIcon/>} onClick={e => addProperty()}>Add property</Button> - </FlexItem>} {isIntegration() && <FlexItem> <Tooltip content="Download image" position={"bottom-end"}> diff --git a/karavan-web/karavan-app/src/main/webui/src/project/file/FileEditor.tsx b/karavan-web/karavan-app/src/main/webui/src/project/file/FileEditor.tsx index 1fc3b67c..9745f2bf 100644 --- a/karavan-web/karavan-app/src/main/webui/src/project/file/FileEditor.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/project/file/FileEditor.tsx @@ -24,6 +24,9 @@ import {KaravanDesigner} from "../../designer/KaravanDesigner"; import {ProjectService} from "../../api/ProjectService"; import {PropertiesTable} from "./PropertiesTable"; import {shallow} from "zustand/shallow"; +import {PropertiesToolbar} from "./PropertiesToolbar"; +import {Card, Panel} from "@patternfly/react-core"; +import {PropertiesPanel} from "./PropertiesPanel"; interface Props { projectId: string @@ -31,8 +34,7 @@ interface Props { export function FileEditor (props: Props) { - const [file, operation, mode] = useFileStore((state) => - [state.file, state.operation, state.mode, state.setMode], shallow ) + const [file, operation] = useFileStore((state) => [state.file, state.operation], shallow ) function save (name: string, code: string) { if (file) { @@ -50,6 +52,7 @@ export function FileEditor (props: Props) { return ( file !== undefined && <KaravanDesigner + showCodeTab={true} dark={false} filename={file.name} yaml={file.code} @@ -86,13 +89,13 @@ export function FileEditor (props: Props) { const isProperties = file !== undefined && file.name.endsWith("properties"); const isScript = file !== undefined && file.name.endsWith("sh"); const isCode = file !== undefined && (file.name.endsWith("java") || file.name.endsWith("groovy") || file.name.endsWith("json")); - const showDesigner = isYaml && isIntegration && mode === 'design'; - const showEditor = isCode || (isYaml && !isIntegration) || (isYaml && mode === 'code') || isScript; + const showDesigner = isYaml && isIntegration; + const showEditor = isCode || (isYaml && !isIntegration) || (isYaml) || isScript; return ( <> {showDesigner && getDesigner()} {showEditor && getEditor()} - {isProperties && file !== undefined && <PropertiesTable/>} + {isProperties && file !== undefined && <PropertiesPanel/>} </> ) } diff --git a/karavan-web/karavan-app/src/main/webui/src/project/file/PropertiesPanel.tsx b/karavan-web/karavan-app/src/main/webui/src/project/file/PropertiesPanel.tsx new file mode 100644 index 00000000..2cd41770 --- /dev/null +++ b/karavan-web/karavan-app/src/main/webui/src/project/file/PropertiesPanel.tsx @@ -0,0 +1,87 @@ +import React, {useEffect} from 'react'; +import { + Badge, + Button, + Bullseye, + EmptyState, + EmptyStateVariant, + EmptyStateIcon, + PageSection, PanelHeader, Panel, Tooltip, Label, EmptyStateHeader, PanelMain, PanelMainBody, Flex, FlexItem, +} from '@patternfly/react-core'; +import '../../designer/karavan.css'; +import { + Tbody, + Td, + Th, + Thead, + Tr +} from '@patternfly/react-table'; +import { + Table +} from '@patternfly/react-table/deprecated'; +import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon"; +import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon'; +import {useFilesStore, useFileStore, useProjectStore} from "../../api/ProjectStore"; +import {getProjectFileType, ProjectFile, ProjectFileTypes} from "../../api/ProjectModels"; +import DownloadIcon from "@patternfly/react-icons/dist/esm/icons/download-icon"; +import FileSaver from "file-saver"; +import {shallow} from "zustand/shallow"; +import {PropertiesToolbar} from "./PropertiesToolbar"; +import {PropertiesTable} from "./PropertiesTable"; + +export function PropertiesPanel () { + + const [files] = useFilesStore((s) => [s.files], shallow); + const [project] = useProjectStore((s) => [s.project], shallow); + const [operation] = useFileStore((s) => [s.operation], shallow); + + function getDate(lastUpdate: number): string { + if (lastUpdate) { + const date = new Date(lastUpdate); + return date.toISOString().slice(0, 19).replace('T',' '); + } else { + return "N/A" + } + } + + function needCommit(lastUpdate: number): boolean { + return lastUpdate > project.lastCommitTimestamp; + } + + function download (file: ProjectFile) { + if (file) { + const type = file.name.endsWith("yaml") ? "application/yaml;charset=utf-8" : undefined; + const f = new File([file.code], file.name, {type: type}); + FileSaver.saveAs(f); + } + } + + function isBuildIn(): boolean { + return ['kamelets', 'templates', 'services'].includes(project.projectId); + } + + function canDeleteFiles(): boolean { + return !['templates', 'services'].includes(project.projectId); + } + + function isKameletsProject(): boolean { + return project.projectId === 'kamelets'; + } + + const types = isBuildIn() + ? (isKameletsProject() ? ['KAMELET'] : ['CODE', 'PROPERTIES']) + : ProjectFileTypes.filter(p => !['PROPERTIES', 'LOG', 'KAMELET'].includes(p.name)).map(p => p.name); + + return ( + <PageSection padding={{default: 'noPadding'}} className="scrollable-out"> + <PageSection isFilled padding={{default: 'padding'}} className="scrollable-in"> + <Panel> + <PanelHeader> + <PropertiesToolbar/> + </PanelHeader> + </Panel> + <PropertiesTable/> + </PageSection> + </PageSection> + ) +} diff --git a/karavan-web/karavan-app/src/main/webui/src/project/file/PropertiesTable.tsx b/karavan-web/karavan-app/src/main/webui/src/project/file/PropertiesTable.tsx index 1e0fe7fb..b443f32c 100644 --- a/karavan-web/karavan-app/src/main/webui/src/project/file/PropertiesTable.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/project/file/PropertiesTable.tsx @@ -22,13 +22,13 @@ import { } from '@patternfly/react-core'; import '../../designer/karavan.css'; import { - Tbody, - Th, - Thead, - Tr + Tbody, + Th, + Thead, + Tr } from '@patternfly/react-table'; import { - Table + Table } from '@patternfly/react-table/deprecated'; import {ProjectModel, ProjectProperty} from "karavan-core/lib/model/ProjectModel"; @@ -38,7 +38,7 @@ import {shallow} from "zustand/shallow" import {PropertyField} from "./PropertyField"; import {ProjectService} from "../../api/ProjectService"; -export function PropertiesTable () { +export function PropertiesTable() { const [showDeleteConfirmation, setShowDeleteConfirmation] = useState<boolean>(false); const [deleteId, setDeleteId] = useState<string | undefined>(undefined); @@ -52,7 +52,7 @@ export function PropertiesTable () { setProperties(getProjectModel().properties) }, [addProperty]); - function save (props: ProjectProperty[]) { + function save(props: ProjectProperty[]) { if (file) { file.code = ProjectModelApi.propertiesToString(props); ProjectService.saveFile(file, true); @@ -100,28 +100,28 @@ export function PropertiesTable () { } return ( - <PageSection isFilled className="scrollable-in" padding={{default: file !== undefined ? 'noPadding' : 'padding'}}> - <PageSection padding={{default: "noPadding"}}> - {properties.length > 0 && - <Table aria-label="Property table" variant='compact' borders={false} - className="project-properties"> - <Thead> - <Tr> - <Th key='name'>Name</Th> - <Th key='value'>Value</Th> - <Th></Th> - </Tr> - </Thead> - <Tbody> - {properties.map((property, idx: number) => { - const readOnly = (property.key.startsWith("camel.jbang") || property.key.startsWith("camel.karavan")) && !editAdvancedProperties; - return ( - <PropertyField property={property} readOnly={readOnly} changeProperty={changeProperty} onDelete={startDelete}/> - )})} - </Tbody> - </Table>} - {showDeleteConfirmation && getDeleteConfirmation()} - </PageSection> - </PageSection> + <> + {properties.length > 0 && + <Table aria-label="Property table" variant='compact' borders={false} + className="project-properties"> + <Thead> + <Tr> + <Th key='name'>Name</Th> + <Th key='value'>Value</Th> + <Th></Th> + </Tr> + </Thead> + <Tbody> + {properties.map((property, idx: number) => { + const readOnly = (property.key.startsWith("camel.jbang") || property.key.startsWith("camel.karavan")) && !editAdvancedProperties; + return ( + <PropertyField property={property} readOnly={readOnly} changeProperty={changeProperty} + onDelete={startDelete}/> + ) + })} + </Tbody> + </Table>} + {showDeleteConfirmation && getDeleteConfirmation()} + </> ) } \ No newline at end of file diff --git a/karavan-web/karavan-app/src/main/webui/src/project/file/PropertiesToolbar.tsx b/karavan-web/karavan-app/src/main/webui/src/project/file/PropertiesToolbar.tsx new file mode 100644 index 00000000..598dc95e --- /dev/null +++ b/karavan-web/karavan-app/src/main/webui/src/project/file/PropertiesToolbar.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 from 'react'; +import { + Button, Checkbox, + Flex, + FlexItem +} from '@patternfly/react-core'; +import '../../designer/karavan.css'; +import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon"; +import {useFileStore} from "../../api/ProjectStore"; +import {shallow} from "zustand/shallow"; +import {ProjectService} from "../../api/ProjectService"; +import {ProjectModelApi} from "karavan-core/lib/api/ProjectModelApi"; +import {ProjectModel, ProjectProperty} from "karavan-core/lib/model/ProjectModel"; + +export function PropertiesToolbar () { + + const [file, editAdvancedProperties, setEditAdvancedProperties, setAddProperty] = useFileStore((state) => + [state.file, state.editAdvancedProperties, state.setEditAdvancedProperties, state.setAddProperty], shallow ) + + + function addProperty() { + if (file) { + const project = file ? ProjectModelApi.propertiesToProject(file?.code) : ProjectModel.createNew(); + const props = project.properties; + props.push(ProjectProperty.createNew("", "")); + file.code = ProjectModelApi.propertiesToString(props); + ProjectService.saveFile(file, true); + setAddProperty(Math.random().toString()); + } + } + + return ( + <Flex className="toolbar" direction={{default: "row"}} justifyContent={{default: "justifyContentFlexEnd"}}> + <FlexItem> + <Checkbox + id="advanced" + label="Edit advanced" + isChecked={editAdvancedProperties} + onChange={(_, checked) => setEditAdvancedProperties(checked)} + /> + </FlexItem> + <FlexItem> + <Button size="sm" variant="primary" icon={<PlusIcon/>} onClick={e => addProperty()}>Add property</Button> + </FlexItem> + </Flex> + ) +}