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 6682daf4 Fix #932 6682daf4 is described below commit 6682daf4e7faeeea97372b6f86f16cbd8b9602cc Author: Marat Gubaidullin <ma...@talismancloud.io> AuthorDate: Fri Oct 6 13:23:51 2023 -0400 Fix #932 --- karavan-designer/src/App.tsx | 4 +- karavan-designer/src/designer/DesignerStore.ts | 7 ++ karavan-designer/src/designer/KaravanDesigner.tsx | 3 +- karavan-designer/src/designer/route/DslElement.tsx | 45 ++---------- .../src/designer/route/DslElementMoveModal.tsx | 81 ++++++++++++++++++++++ .../src/designer/route/RouteDesigner.tsx | 7 +- karavan-space/src/designer/DesignerStore.ts | 7 ++ karavan-space/src/designer/KaravanDesigner.tsx | 3 +- karavan-space/src/designer/rest/rest.css | 5 +- karavan-space/src/designer/route/DslElement.tsx | 45 ++---------- .../src/designer/route/DslElementMoveModal.tsx | 81 ++++++++++++++++++++++ karavan-space/src/designer/route/DslProperties.tsx | 3 +- karavan-space/src/designer/route/RouteDesigner.tsx | 7 +- .../src/main/webui/src/designer/DesignerStore.ts | 7 ++ .../main/webui/src/designer/KaravanDesigner.tsx | 3 +- .../main/webui/src/designer/route/DslElement.tsx | 45 ++---------- .../src/designer/route/DslElementMoveModal.tsx | 81 ++++++++++++++++++++++ .../webui/src/designer/route/DslProperties.tsx | 3 +- .../webui/src/designer/route/RouteDesigner.tsx | 7 +- 19 files changed, 304 insertions(+), 140 deletions(-) diff --git a/karavan-designer/src/App.tsx b/karavan-designer/src/App.tsx index f23eb43d..9e760223 100644 --- a/karavan-designer/src/App.tsx +++ b/karavan-designer/src/App.tsx @@ -69,8 +69,8 @@ class App extends React.Component<Props, State> { fetch("components/components.json"), fetch("snippets/org.apache.camel.AggregationStrategy"), fetch("snippets/org.apache.camel.Processor"), - // fetch("example/demo.camel.yaml") - fetch("example/aws-s3-cdc-source.kamelet.yaml") + fetch("example/demo.camel.yaml") + // fetch("example/aws-s3-cdc-source.kamelet.yaml") // fetch("components/supported-components.json"), ]).then(responses => Promise.all(responses.map(response => response.text())) diff --git a/karavan-designer/src/designer/DesignerStore.ts b/karavan-designer/src/designer/DesignerStore.ts index 15a9b7be..4831855f 100644 --- a/karavan-designer/src/designer/DesignerStore.ts +++ b/karavan-designer/src/designer/DesignerStore.ts @@ -166,7 +166,9 @@ type DesignerState = { height: number, top: number, left: number, + moveElements: [string | undefined, string | undefined] } + const designerState: DesignerState = { notificationBadge: false, notificationMessage: ['', ''], @@ -182,6 +184,7 @@ const designerState: DesignerState = { height: 0, top: 0, left: 0, + moveElements: [undefined, undefined] }; type DesignerAction = { @@ -197,6 +200,7 @@ type DesignerAction = { setPosition: (width: number, height: number, top: number, left: number) => void; reset: () => void; setNotification: (notificationBadge: boolean, notificationMessage: [string, string]) => void; + setMoveElements: (moveElements: [string | undefined, string | undefined]) => void; } export const useDesignerStore = createWithEqualityFn<DesignerState & DesignerAction>((set) => ({ @@ -248,5 +252,8 @@ export const useDesignerStore = createWithEqualityFn<DesignerState & DesignerAct }, setNotification: (notificationBadge: boolean, notificationMessage: [string, string]) => { set({notificationBadge: notificationBadge, notificationMessage: notificationMessage}) + }, + setMoveElements: (moveElements: [string | undefined, string | undefined]) => { + set({moveElements: moveElements}) } }), shallow) \ No newline at end of file diff --git a/karavan-designer/src/designer/KaravanDesigner.tsx b/karavan-designer/src/designer/KaravanDesigner.tsx index 71914986..766b4e1f 100644 --- a/karavan-designer/src/designer/KaravanDesigner.tsx +++ b/karavan-designer/src/designer/KaravanDesigner.tsx @@ -134,7 +134,8 @@ export function KaravanDesigner(props: Props) { const isKamelet = integration.type === 'kamelet'; return ( - <PageSection variant={props.dark ? PageSectionVariants.darker : PageSectionVariants.light} className="page" + <PageSection variant={props.dark ? PageSectionVariants.darker : PageSectionVariants.light} + className="page" isFilled padding={{default: 'noPadding'}}> <div className={"main-tabs-wrapper"}> <Tabs className="main-tabs" diff --git a/karavan-designer/src/designer/route/DslElement.tsx b/karavan-designer/src/designer/route/DslElement.tsx index 5f2744c5..0d246b20 100644 --- a/karavan-designer/src/designer/route/DslElement.tsx +++ b/karavan-designer/src/designer/route/DslElement.tsx @@ -16,9 +16,6 @@ */ import React, {CSSProperties, useMemo, useState} from 'react'; import { - Button, - Flex, - Modal, ModalVariant, Text, Tooltip, } from '@patternfly/react-core'; import '../karavan.css'; @@ -49,13 +46,12 @@ export function DslElement(props: Props) { const [integration] = useIntegrationStore((s) => [s.integration, s.setIntegration], shallow) - const [selectedUuids, selectedStep, showMoveConfirmation, setShowMoveConfirmation, hideLogDSL] = + const [selectedUuids, setShowMoveConfirmation, hideLogDSL, setMoveElements] = useDesignerStore((s) => - [s.selectedUuids, s.selectedStep, s.showMoveConfirmation, s.setShowMoveConfirmation, s.hideLogDSL], shallow) + [s.selectedUuids, s.setShowMoveConfirmation, s.hideLogDSL, s.setMoveElements], shallow) const [isDragging, setIsDragging] = useState<boolean>(false); const [isDraggedOver, setIsDraggedOver] = useState<boolean>(false); - const [moveElements, setMoveElements] = useState<[string | undefined, string | undefined]>([undefined, undefined]); function onOpenSelector(evt: React.MouseEvent, showSteps: boolean = true, isInsert: boolean = false) { evt.stopPropagation(); @@ -92,20 +88,6 @@ export function DslElement(props: Props) { } } - function confirmMove(asChild: boolean) { - const sourceUuid = moveElements[0]; - const targetUuid = moveElements[1]; - if (sourceUuid && targetUuid && sourceUuid !== targetUuid) { - moveElement(sourceUuid, targetUuid, asChild); - cancelMove(); - } - } - - function cancelMove() { - setShowMoveConfirmation(false); - setMoveElements([undefined, undefined]); - } - function isElementSelected(): boolean { return selectedUuids.includes(props.step.uuid); } @@ -388,7 +370,8 @@ export function DslElement(props: Props) { function getAddElementButton() { return ( - <Tooltip position={"bottom"} content={<div>{"Add DSL element to " + CamelDisplayUtil.getTitle(props.step)}</div>}> + <Tooltip position={"bottom"} + content={<div>{"Add DSL element to " + CamelDisplayUtil.getTitle(props.step)}</div>}> <button type="button" aria-label="Add" @@ -419,25 +402,6 @@ export function DslElement(props: Props) { ) } - function getMoveConfirmation() { - return ( - <Modal - aria-label="title" - className='move-modal' - isOpen={showMoveConfirmation} - variant={ModalVariant.small} - ><Flex direction={{default: "column"}}> - <div>Select move type:</div> - <Button key="place" variant="primary" onClick={event => confirmMove(false)}>Shift (target down)</Button> - <Button key="child" variant="secondary" onClick={event => confirmMove(true)}>Move as target - step</Button> - <Button key="cancel" variant="tertiary" onClick={event => cancelMove()}>Cancel</Button> - </Flex> - - </Modal> - ) - } - const element: CamelElement = props.step; const className = "step-element" + (isElementSelected() ? " step-element-selected" : "") + (!props.step.showChildren ? " hidden-step" : ""); return ( @@ -487,7 +451,6 @@ export function DslElement(props: Props) { > {getElementHeader()} {getChildElements()} - {getMoveConfirmation()} </div> ) } diff --git a/karavan-designer/src/designer/route/DslElementMoveModal.tsx b/karavan-designer/src/designer/route/DslElementMoveModal.tsx new file mode 100644 index 00000000..d1e17ecf --- /dev/null +++ b/karavan-designer/src/designer/route/DslElementMoveModal.tsx @@ -0,0 +1,81 @@ +/* + * 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, + Flex, + Modal, ModalVariant, +} from '@patternfly/react-core'; +import '../karavan.css'; +import {useDesignerStore, useIntegrationStore} from "../DesignerStore"; +import {shallow} from "zustand/shallow"; +import {useRouteDesignerHook} from "./useRouteDesignerHook"; +import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt"; + +export function DslElementMoveModal() { + + const {moveElement} = useRouteDesignerHook(); + const [integration] = useIntegrationStore((s) => [s.integration, s.setIntegration], shallow) + const [ showMoveConfirmation, setShowMoveConfirmation, moveElements, setMoveElements] = + useDesignerStore((s) => + [s.showMoveConfirmation, s.setShowMoveConfirmation, s.moveElements, s.setMoveElements], shallow) + + function confirmMove(asChild: boolean) { + const sourceUuid = moveElements[0]; + const targetUuid = moveElements[1]; + if (sourceUuid && targetUuid && sourceUuid !== targetUuid) { + moveElement(sourceUuid, targetUuid, asChild); + cancelMove(); + } + } + + function cancelMove() { + setShowMoveConfirmation(false); + setMoveElements([undefined, undefined]); + } + + function canReplace() { + const targetUuid = moveElements[1]; + if (targetUuid) { + const targetElement = CamelDefinitionApiExt.findElementInIntegration(integration, targetUuid); + if (targetElement) { + return !['WhenDefinition', 'OtherwiseDefinition'].includes(targetElement?.dslName); + } + } + return true; + } + + return ( + <Modal + aria-label="title" + className='move-modal' + isOpen={showMoveConfirmation} + onClose={event => cancelMove()} + variant={ModalVariant.small} + > + <Flex direction={{default: "column"}}> + <div>Select move type:</div> + {canReplace() && <Button key="place" variant="primary" onClick={event => confirmMove(false)} + > + Replace (target down) + </Button>} + <Button key="child" variant="secondary" onClick={event => confirmMove(true)}>Set as child</Button> + <Button key="cancel" variant="tertiary" onClick={event => cancelMove()}>Cancel</Button> + </Flex> + </Modal> + ) +} diff --git a/karavan-designer/src/designer/route/RouteDesigner.tsx b/karavan-designer/src/designer/route/RouteDesigner.tsx index 7d3b5950..215f334b 100644 --- a/karavan-designer/src/designer/route/RouteDesigner.tsx +++ b/karavan-designer/src/designer/route/RouteDesigner.tsx @@ -36,14 +36,16 @@ import useResizeObserver from "./useResizeObserver"; import {Command, EventBus} from "../utils/EventBus"; import useMutationsObserver from "./useDrawerMutationsObserver"; import {DeleteConfirmation} from "./DeleteConfirmation"; +import {DslElementMoveModal} from "./DslElementMoveModal"; export function RouteDesigner() { const {openSelector, createRouteConfiguration, onCommand, handleKeyDown, handleKeyUp, unselectElement} = useRouteDesignerHook(); const [integration] = useIntegrationStore((state) => [state.integration], shallow) - const [showDeleteConfirmation, setPosition, width, height, top, left, hideLogDSL] = useDesignerStore((s) => - [s.showDeleteConfirmation, s.setPosition, s.width, s.height, s.top, s.left, s.hideLogDSL], shallow) + const [showDeleteConfirmation, setPosition, width, height, top, left, hideLogDSL, showMoveConfirmation, setShowMoveConfirmation] = + useDesignerStore((s) => + [s.showDeleteConfirmation, s.setPosition, s.width, s.height, s.top, s.left, s.hideLogDSL, s.showMoveConfirmation, s.setShowMoveConfirmation], shallow) const [showSelector] = useSelectorStore((s) => [s.showSelector], shallow) @@ -161,6 +163,7 @@ export function RouteDesigner() { </div> {showSelector && <DslSelector/>} {showDeleteConfirmation && <DeleteConfirmation/>} + {showMoveConfirmation && <DslElementMoveModal/>} </div> ) } \ No newline at end of file diff --git a/karavan-space/src/designer/DesignerStore.ts b/karavan-space/src/designer/DesignerStore.ts index 15a9b7be..4831855f 100644 --- a/karavan-space/src/designer/DesignerStore.ts +++ b/karavan-space/src/designer/DesignerStore.ts @@ -166,7 +166,9 @@ type DesignerState = { height: number, top: number, left: number, + moveElements: [string | undefined, string | undefined] } + const designerState: DesignerState = { notificationBadge: false, notificationMessage: ['', ''], @@ -182,6 +184,7 @@ const designerState: DesignerState = { height: 0, top: 0, left: 0, + moveElements: [undefined, undefined] }; type DesignerAction = { @@ -197,6 +200,7 @@ type DesignerAction = { setPosition: (width: number, height: number, top: number, left: number) => void; reset: () => void; setNotification: (notificationBadge: boolean, notificationMessage: [string, string]) => void; + setMoveElements: (moveElements: [string | undefined, string | undefined]) => void; } export const useDesignerStore = createWithEqualityFn<DesignerState & DesignerAction>((set) => ({ @@ -248,5 +252,8 @@ export const useDesignerStore = createWithEqualityFn<DesignerState & DesignerAct }, setNotification: (notificationBadge: boolean, notificationMessage: [string, string]) => { set({notificationBadge: notificationBadge, notificationMessage: notificationMessage}) + }, + setMoveElements: (moveElements: [string | undefined, string | undefined]) => { + set({moveElements: moveElements}) } }), shallow) \ No newline at end of file diff --git a/karavan-space/src/designer/KaravanDesigner.tsx b/karavan-space/src/designer/KaravanDesigner.tsx index 71914986..766b4e1f 100644 --- a/karavan-space/src/designer/KaravanDesigner.tsx +++ b/karavan-space/src/designer/KaravanDesigner.tsx @@ -134,7 +134,8 @@ export function KaravanDesigner(props: Props) { const isKamelet = integration.type === 'kamelet'; return ( - <PageSection variant={props.dark ? PageSectionVariants.darker : PageSectionVariants.light} className="page" + <PageSection variant={props.dark ? PageSectionVariants.darker : PageSectionVariants.light} + className="page" isFilled padding={{default: 'noPadding'}}> <div className={"main-tabs-wrapper"}> <Tabs className="main-tabs" diff --git a/karavan-space/src/designer/rest/rest.css b/karavan-space/src/designer/rest/rest.css index 25012e2d..3f1a53a5 100644 --- a/karavan-space/src/designer/rest/rest.css +++ b/karavan-space/src/designer/rest/rest.css @@ -185,6 +185,7 @@ margin-left: 6px; cursor: pointer; justify-content: space-between; + position: relative; } .karavan .rest-designer .rest-config-card, @@ -238,8 +239,8 @@ .karavan .rest-designer .rest-card .delete-button, .karavan .rest-designer .method-card .delete-button { position: absolute; - top: 3px; - right: 3px; + top: -7px; + right: -7px; line-height: 1; border: 0; padding: 0; diff --git a/karavan-space/src/designer/route/DslElement.tsx b/karavan-space/src/designer/route/DslElement.tsx index 5f2744c5..0d246b20 100644 --- a/karavan-space/src/designer/route/DslElement.tsx +++ b/karavan-space/src/designer/route/DslElement.tsx @@ -16,9 +16,6 @@ */ import React, {CSSProperties, useMemo, useState} from 'react'; import { - Button, - Flex, - Modal, ModalVariant, Text, Tooltip, } from '@patternfly/react-core'; import '../karavan.css'; @@ -49,13 +46,12 @@ export function DslElement(props: Props) { const [integration] = useIntegrationStore((s) => [s.integration, s.setIntegration], shallow) - const [selectedUuids, selectedStep, showMoveConfirmation, setShowMoveConfirmation, hideLogDSL] = + const [selectedUuids, setShowMoveConfirmation, hideLogDSL, setMoveElements] = useDesignerStore((s) => - [s.selectedUuids, s.selectedStep, s.showMoveConfirmation, s.setShowMoveConfirmation, s.hideLogDSL], shallow) + [s.selectedUuids, s.setShowMoveConfirmation, s.hideLogDSL, s.setMoveElements], shallow) const [isDragging, setIsDragging] = useState<boolean>(false); const [isDraggedOver, setIsDraggedOver] = useState<boolean>(false); - const [moveElements, setMoveElements] = useState<[string | undefined, string | undefined]>([undefined, undefined]); function onOpenSelector(evt: React.MouseEvent, showSteps: boolean = true, isInsert: boolean = false) { evt.stopPropagation(); @@ -92,20 +88,6 @@ export function DslElement(props: Props) { } } - function confirmMove(asChild: boolean) { - const sourceUuid = moveElements[0]; - const targetUuid = moveElements[1]; - if (sourceUuid && targetUuid && sourceUuid !== targetUuid) { - moveElement(sourceUuid, targetUuid, asChild); - cancelMove(); - } - } - - function cancelMove() { - setShowMoveConfirmation(false); - setMoveElements([undefined, undefined]); - } - function isElementSelected(): boolean { return selectedUuids.includes(props.step.uuid); } @@ -388,7 +370,8 @@ export function DslElement(props: Props) { function getAddElementButton() { return ( - <Tooltip position={"bottom"} content={<div>{"Add DSL element to " + CamelDisplayUtil.getTitle(props.step)}</div>}> + <Tooltip position={"bottom"} + content={<div>{"Add DSL element to " + CamelDisplayUtil.getTitle(props.step)}</div>}> <button type="button" aria-label="Add" @@ -419,25 +402,6 @@ export function DslElement(props: Props) { ) } - function getMoveConfirmation() { - return ( - <Modal - aria-label="title" - className='move-modal' - isOpen={showMoveConfirmation} - variant={ModalVariant.small} - ><Flex direction={{default: "column"}}> - <div>Select move type:</div> - <Button key="place" variant="primary" onClick={event => confirmMove(false)}>Shift (target down)</Button> - <Button key="child" variant="secondary" onClick={event => confirmMove(true)}>Move as target - step</Button> - <Button key="cancel" variant="tertiary" onClick={event => cancelMove()}>Cancel</Button> - </Flex> - - </Modal> - ) - } - const element: CamelElement = props.step; const className = "step-element" + (isElementSelected() ? " step-element-selected" : "") + (!props.step.showChildren ? " hidden-step" : ""); return ( @@ -487,7 +451,6 @@ export function DslElement(props: Props) { > {getElementHeader()} {getChildElements()} - {getMoveConfirmation()} </div> ) } diff --git a/karavan-space/src/designer/route/DslElementMoveModal.tsx b/karavan-space/src/designer/route/DslElementMoveModal.tsx new file mode 100644 index 00000000..d1e17ecf --- /dev/null +++ b/karavan-space/src/designer/route/DslElementMoveModal.tsx @@ -0,0 +1,81 @@ +/* + * 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, + Flex, + Modal, ModalVariant, +} from '@patternfly/react-core'; +import '../karavan.css'; +import {useDesignerStore, useIntegrationStore} from "../DesignerStore"; +import {shallow} from "zustand/shallow"; +import {useRouteDesignerHook} from "./useRouteDesignerHook"; +import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt"; + +export function DslElementMoveModal() { + + const {moveElement} = useRouteDesignerHook(); + const [integration] = useIntegrationStore((s) => [s.integration, s.setIntegration], shallow) + const [ showMoveConfirmation, setShowMoveConfirmation, moveElements, setMoveElements] = + useDesignerStore((s) => + [s.showMoveConfirmation, s.setShowMoveConfirmation, s.moveElements, s.setMoveElements], shallow) + + function confirmMove(asChild: boolean) { + const sourceUuid = moveElements[0]; + const targetUuid = moveElements[1]; + if (sourceUuid && targetUuid && sourceUuid !== targetUuid) { + moveElement(sourceUuid, targetUuid, asChild); + cancelMove(); + } + } + + function cancelMove() { + setShowMoveConfirmation(false); + setMoveElements([undefined, undefined]); + } + + function canReplace() { + const targetUuid = moveElements[1]; + if (targetUuid) { + const targetElement = CamelDefinitionApiExt.findElementInIntegration(integration, targetUuid); + if (targetElement) { + return !['WhenDefinition', 'OtherwiseDefinition'].includes(targetElement?.dslName); + } + } + return true; + } + + return ( + <Modal + aria-label="title" + className='move-modal' + isOpen={showMoveConfirmation} + onClose={event => cancelMove()} + variant={ModalVariant.small} + > + <Flex direction={{default: "column"}}> + <div>Select move type:</div> + {canReplace() && <Button key="place" variant="primary" onClick={event => confirmMove(false)} + > + Replace (target down) + </Button>} + <Button key="child" variant="secondary" onClick={event => confirmMove(true)}>Set as child</Button> + <Button key="cancel" variant="tertiary" onClick={event => cancelMove()}>Cancel</Button> + </Flex> + </Modal> + ) +} diff --git a/karavan-space/src/designer/route/DslProperties.tsx b/karavan-space/src/designer/route/DslProperties.tsx index 82fd3dd9..8231759c 100644 --- a/karavan-space/src/designer/route/DslProperties.tsx +++ b/karavan-space/src/designer/route/DslProperties.tsx @@ -41,8 +41,7 @@ interface Props { export function DslProperties(props: Props) { - const [integration, setIntegration] = useIntegrationStore((state) => - [state.integration, state.setIntegration], shallow) + const [integration] = useIntegrationStore((state) => [state.integration], shallow) const {cloneElement, onDataFormatChange, onPropertyChange, onParametersChange, onExpressionChange} = usePropertiesHook(props.isRouteDesigner); diff --git a/karavan-space/src/designer/route/RouteDesigner.tsx b/karavan-space/src/designer/route/RouteDesigner.tsx index 7d3b5950..215f334b 100644 --- a/karavan-space/src/designer/route/RouteDesigner.tsx +++ b/karavan-space/src/designer/route/RouteDesigner.tsx @@ -36,14 +36,16 @@ import useResizeObserver from "./useResizeObserver"; import {Command, EventBus} from "../utils/EventBus"; import useMutationsObserver from "./useDrawerMutationsObserver"; import {DeleteConfirmation} from "./DeleteConfirmation"; +import {DslElementMoveModal} from "./DslElementMoveModal"; export function RouteDesigner() { const {openSelector, createRouteConfiguration, onCommand, handleKeyDown, handleKeyUp, unselectElement} = useRouteDesignerHook(); const [integration] = useIntegrationStore((state) => [state.integration], shallow) - const [showDeleteConfirmation, setPosition, width, height, top, left, hideLogDSL] = useDesignerStore((s) => - [s.showDeleteConfirmation, s.setPosition, s.width, s.height, s.top, s.left, s.hideLogDSL], shallow) + const [showDeleteConfirmation, setPosition, width, height, top, left, hideLogDSL, showMoveConfirmation, setShowMoveConfirmation] = + useDesignerStore((s) => + [s.showDeleteConfirmation, s.setPosition, s.width, s.height, s.top, s.left, s.hideLogDSL, s.showMoveConfirmation, s.setShowMoveConfirmation], shallow) const [showSelector] = useSelectorStore((s) => [s.showSelector], shallow) @@ -161,6 +163,7 @@ export function RouteDesigner() { </div> {showSelector && <DslSelector/>} {showDeleteConfirmation && <DeleteConfirmation/>} + {showMoveConfirmation && <DslElementMoveModal/>} </div> ) } \ No newline at end of file diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/DesignerStore.ts b/karavan-web/karavan-app/src/main/webui/src/designer/DesignerStore.ts index 15a9b7be..4831855f 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/DesignerStore.ts +++ b/karavan-web/karavan-app/src/main/webui/src/designer/DesignerStore.ts @@ -166,7 +166,9 @@ type DesignerState = { height: number, top: number, left: number, + moveElements: [string | undefined, string | undefined] } + const designerState: DesignerState = { notificationBadge: false, notificationMessage: ['', ''], @@ -182,6 +184,7 @@ const designerState: DesignerState = { height: 0, top: 0, left: 0, + moveElements: [undefined, undefined] }; type DesignerAction = { @@ -197,6 +200,7 @@ type DesignerAction = { setPosition: (width: number, height: number, top: number, left: number) => void; reset: () => void; setNotification: (notificationBadge: boolean, notificationMessage: [string, string]) => void; + setMoveElements: (moveElements: [string | undefined, string | undefined]) => void; } export const useDesignerStore = createWithEqualityFn<DesignerState & DesignerAction>((set) => ({ @@ -248,5 +252,8 @@ export const useDesignerStore = createWithEqualityFn<DesignerState & DesignerAct }, setNotification: (notificationBadge: boolean, notificationMessage: [string, string]) => { set({notificationBadge: notificationBadge, notificationMessage: notificationMessage}) + }, + setMoveElements: (moveElements: [string | undefined, string | undefined]) => { + set({moveElements: moveElements}) } }), shallow) \ No newline at end of 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 71914986..766b4e1f 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 @@ -134,7 +134,8 @@ export function KaravanDesigner(props: Props) { const isKamelet = integration.type === 'kamelet'; return ( - <PageSection variant={props.dark ? PageSectionVariants.darker : PageSectionVariants.light} className="page" + <PageSection variant={props.dark ? PageSectionVariants.darker : PageSectionVariants.light} + className="page" isFilled padding={{default: 'noPadding'}}> <div className={"main-tabs-wrapper"}> <Tabs className="main-tabs" diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/DslElement.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/route/DslElement.tsx index 5f2744c5..0d246b20 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/route/DslElement.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/DslElement.tsx @@ -16,9 +16,6 @@ */ import React, {CSSProperties, useMemo, useState} from 'react'; import { - Button, - Flex, - Modal, ModalVariant, Text, Tooltip, } from '@patternfly/react-core'; import '../karavan.css'; @@ -49,13 +46,12 @@ export function DslElement(props: Props) { const [integration] = useIntegrationStore((s) => [s.integration, s.setIntegration], shallow) - const [selectedUuids, selectedStep, showMoveConfirmation, setShowMoveConfirmation, hideLogDSL] = + const [selectedUuids, setShowMoveConfirmation, hideLogDSL, setMoveElements] = useDesignerStore((s) => - [s.selectedUuids, s.selectedStep, s.showMoveConfirmation, s.setShowMoveConfirmation, s.hideLogDSL], shallow) + [s.selectedUuids, s.setShowMoveConfirmation, s.hideLogDSL, s.setMoveElements], shallow) const [isDragging, setIsDragging] = useState<boolean>(false); const [isDraggedOver, setIsDraggedOver] = useState<boolean>(false); - const [moveElements, setMoveElements] = useState<[string | undefined, string | undefined]>([undefined, undefined]); function onOpenSelector(evt: React.MouseEvent, showSteps: boolean = true, isInsert: boolean = false) { evt.stopPropagation(); @@ -92,20 +88,6 @@ export function DslElement(props: Props) { } } - function confirmMove(asChild: boolean) { - const sourceUuid = moveElements[0]; - const targetUuid = moveElements[1]; - if (sourceUuid && targetUuid && sourceUuid !== targetUuid) { - moveElement(sourceUuid, targetUuid, asChild); - cancelMove(); - } - } - - function cancelMove() { - setShowMoveConfirmation(false); - setMoveElements([undefined, undefined]); - } - function isElementSelected(): boolean { return selectedUuids.includes(props.step.uuid); } @@ -388,7 +370,8 @@ export function DslElement(props: Props) { function getAddElementButton() { return ( - <Tooltip position={"bottom"} content={<div>{"Add DSL element to " + CamelDisplayUtil.getTitle(props.step)}</div>}> + <Tooltip position={"bottom"} + content={<div>{"Add DSL element to " + CamelDisplayUtil.getTitle(props.step)}</div>}> <button type="button" aria-label="Add" @@ -419,25 +402,6 @@ export function DslElement(props: Props) { ) } - function getMoveConfirmation() { - return ( - <Modal - aria-label="title" - className='move-modal' - isOpen={showMoveConfirmation} - variant={ModalVariant.small} - ><Flex direction={{default: "column"}}> - <div>Select move type:</div> - <Button key="place" variant="primary" onClick={event => confirmMove(false)}>Shift (target down)</Button> - <Button key="child" variant="secondary" onClick={event => confirmMove(true)}>Move as target - step</Button> - <Button key="cancel" variant="tertiary" onClick={event => cancelMove()}>Cancel</Button> - </Flex> - - </Modal> - ) - } - const element: CamelElement = props.step; const className = "step-element" + (isElementSelected() ? " step-element-selected" : "") + (!props.step.showChildren ? " hidden-step" : ""); return ( @@ -487,7 +451,6 @@ export function DslElement(props: Props) { > {getElementHeader()} {getChildElements()} - {getMoveConfirmation()} </div> ) } diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/DslElementMoveModal.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/route/DslElementMoveModal.tsx new file mode 100644 index 00000000..d1e17ecf --- /dev/null +++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/DslElementMoveModal.tsx @@ -0,0 +1,81 @@ +/* + * 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, + Flex, + Modal, ModalVariant, +} from '@patternfly/react-core'; +import '../karavan.css'; +import {useDesignerStore, useIntegrationStore} from "../DesignerStore"; +import {shallow} from "zustand/shallow"; +import {useRouteDesignerHook} from "./useRouteDesignerHook"; +import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt"; + +export function DslElementMoveModal() { + + const {moveElement} = useRouteDesignerHook(); + const [integration] = useIntegrationStore((s) => [s.integration, s.setIntegration], shallow) + const [ showMoveConfirmation, setShowMoveConfirmation, moveElements, setMoveElements] = + useDesignerStore((s) => + [s.showMoveConfirmation, s.setShowMoveConfirmation, s.moveElements, s.setMoveElements], shallow) + + function confirmMove(asChild: boolean) { + const sourceUuid = moveElements[0]; + const targetUuid = moveElements[1]; + if (sourceUuid && targetUuid && sourceUuid !== targetUuid) { + moveElement(sourceUuid, targetUuid, asChild); + cancelMove(); + } + } + + function cancelMove() { + setShowMoveConfirmation(false); + setMoveElements([undefined, undefined]); + } + + function canReplace() { + const targetUuid = moveElements[1]; + if (targetUuid) { + const targetElement = CamelDefinitionApiExt.findElementInIntegration(integration, targetUuid); + if (targetElement) { + return !['WhenDefinition', 'OtherwiseDefinition'].includes(targetElement?.dslName); + } + } + return true; + } + + return ( + <Modal + aria-label="title" + className='move-modal' + isOpen={showMoveConfirmation} + onClose={event => cancelMove()} + variant={ModalVariant.small} + > + <Flex direction={{default: "column"}}> + <div>Select move type:</div> + {canReplace() && <Button key="place" variant="primary" onClick={event => confirmMove(false)} + > + Replace (target down) + </Button>} + <Button key="child" variant="secondary" onClick={event => confirmMove(true)}>Set as child</Button> + <Button key="cancel" variant="tertiary" onClick={event => cancelMove()}>Cancel</Button> + </Flex> + </Modal> + ) +} diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/DslProperties.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/route/DslProperties.tsx index 82fd3dd9..8231759c 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/route/DslProperties.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/DslProperties.tsx @@ -41,8 +41,7 @@ interface Props { export function DslProperties(props: Props) { - const [integration, setIntegration] = useIntegrationStore((state) => - [state.integration, state.setIntegration], shallow) + const [integration] = useIntegrationStore((state) => [state.integration], shallow) const {cloneElement, onDataFormatChange, onPropertyChange, onParametersChange, onExpressionChange} = usePropertiesHook(props.isRouteDesigner); diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/RouteDesigner.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/route/RouteDesigner.tsx index 7d3b5950..215f334b 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/route/RouteDesigner.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/RouteDesigner.tsx @@ -36,14 +36,16 @@ import useResizeObserver from "./useResizeObserver"; import {Command, EventBus} from "../utils/EventBus"; import useMutationsObserver from "./useDrawerMutationsObserver"; import {DeleteConfirmation} from "./DeleteConfirmation"; +import {DslElementMoveModal} from "./DslElementMoveModal"; export function RouteDesigner() { const {openSelector, createRouteConfiguration, onCommand, handleKeyDown, handleKeyUp, unselectElement} = useRouteDesignerHook(); const [integration] = useIntegrationStore((state) => [state.integration], shallow) - const [showDeleteConfirmation, setPosition, width, height, top, left, hideLogDSL] = useDesignerStore((s) => - [s.showDeleteConfirmation, s.setPosition, s.width, s.height, s.top, s.left, s.hideLogDSL], shallow) + const [showDeleteConfirmation, setPosition, width, height, top, left, hideLogDSL, showMoveConfirmation, setShowMoveConfirmation] = + useDesignerStore((s) => + [s.showDeleteConfirmation, s.setPosition, s.width, s.height, s.top, s.left, s.hideLogDSL, s.showMoveConfirmation, s.setShowMoveConfirmation], shallow) const [showSelector] = useSelectorStore((s) => [s.showSelector], shallow) @@ -161,6 +163,7 @@ export function RouteDesigner() { </div> {showSelector && <DslSelector/>} {showDeleteConfirmation && <DeleteConfirmation/>} + {showMoveConfirmation && <DslElementMoveModal/>} </div> ) } \ No newline at end of file