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 1456d14b Fix #623
1456d14b is described below

commit 1456d14bfc4b15a6517e3fb405ef8a30d4d0f5f0
Author: Marat Gubaidullin <marat.gubaidul...@gmail.com>
AuthorDate: Fri Jan 20 18:35:32 2023 -0500

    Fix #623
---
 karavan-designer/src/designer/route/DslElement.tsx |  36 ++---
 .../src/designer/route/RouteDesigner.tsx           | 169 ++++++++++++++-------
 2 files changed, 126 insertions(+), 79 deletions(-)

diff --git a/karavan-designer/src/designer/route/DslElement.tsx 
b/karavan-designer/src/designer/route/DslElement.tsx
index 07222d84..f9cd9498 100644
--- a/karavan-designer/src/designer/route/DslElement.tsx
+++ b/karavan-designer/src/designer/route/DslElement.tsx
@@ -41,7 +41,7 @@ interface Props {
     selectElement: any
     openSelector: (parentId: string | undefined, parentDsl: string | 
undefined, showSteps: boolean, position?: number | undefined) => void
     moveElement: (source: string, target: string, asChild: boolean) => void
-    selectedUuid: string
+    selectedUuid: string []
     inSteps: boolean
     position: number
 }
@@ -51,7 +51,6 @@ interface State {
     showMoveConfirmation: boolean
     moveElements: [string | undefined, string | undefined]
     tabIndex: string | number
-    selectedUuid: string
     isDragging: boolean
     isDraggedOver: boolean
 }
@@ -63,29 +62,16 @@ export class DslElement extends React.Component<Props, 
State> {
         showMoveConfirmation: false,
         moveElements: [undefined, undefined],
         tabIndex: 0,
-        selectedUuid: this.props.selectedUuid,
         isDragging: false,
         isDraggedOver: false,
     };
 
-    handleKeyDown = (event: React.KeyboardEvent) =>{
-        // event.preventDefault();
-        // console.log(event);
-        // let charCode = String.fromCharCode(event.which).toLowerCase();
-        // if((event.ctrlKey || event.metaKey) && charCode === 's') {
-        //     alert("CTRL+S Pressed");
-        // }else if((event.ctrlKey || event.metaKey) && charCode === 'c') {
-        //     alert("CTRL+C Pressed");
-        // }else if((event.ctrlKey || event.metaKey) && charCode === 'v') {
-        //     alert("CTRL+V Pressed");
-        // }
-    }
-
-    componentDidUpdate = (prevProps: Readonly<Props>, prevState: 
Readonly<State>, snapshot?: any) => {
-        if (prevState.selectedUuid !== this.props.selectedUuid) {
-            this.setState({selectedUuid: this.props.selectedUuid});
-        }
-    }
+    //
+    // componentDidUpdate = (prevProps: Readonly<Props>, prevState: 
Readonly<State>, snapshot?: any) => {
+    //     if (prevState.selectedUuid !== this.props.selectedUuid) {
+    //         this.setState({selectedUuid: this.props.selectedUuid});
+    //     }
+    // }
 
     openSelector = (evt: React.MouseEvent, showSteps: boolean = true, 
isInsert: boolean = false) => {
         evt.stopPropagation();
@@ -139,7 +125,7 @@ export class DslElement extends React.Component<Props, 
State> {
     }
 
     isSelected = (): boolean => {
-        return this.state.selectedUuid === this.props.step.uuid
+        return this.props.selectedUuid.includes(this.props.step.uuid);
     }
 
     hasBorder = (): boolean => {
@@ -365,7 +351,7 @@ export class DslElement extends React.Component<Props, 
State> {
                                 deleteElement={this.props.deleteElement}
                                 selectElement={this.props.selectElement}
                                 moveElement={this.props.moveElement}
-                                selectedUuid={this.state.selectedUuid}
+                                selectedUuid={this.props.selectedUuid}
                                 inSteps={child.name === 'steps'}
                                 position={index}
                                 step={element}
@@ -386,7 +372,7 @@ export class DslElement extends React.Component<Props, 
State> {
 
     getAddStepButton() {
         const {integration, step, selectedUuid} = this.props;
-        const hideAddButton = step.dslName === 'StepDefinition' && 
!CamelDisplayUtil.isStepDefinitionExpanded(integration, step.uuid, 
selectedUuid);
+        const hideAddButton = step.dslName === 'StepDefinition' && 
!CamelDisplayUtil.isStepDefinitionExpanded(integration, step.uuid, 
selectedUuid.at(0));
         if (hideAddButton) return (<></>)
         else return (
             <Tooltip position={"bottom"}
@@ -497,8 +483,6 @@ export class DslElement extends React.Component<Props, 
State> {
                  }}
                  onDrop={event => this.dragElement(event, element)}
                  draggable={!this.isNotDraggable()}
-                 // tabIndex={0}
-                 onKeyDown={this.handleKeyDown}
             >
                 {this.getElementHeader()}
                 {this.getChildElements()}
diff --git a/karavan-designer/src/designer/route/RouteDesigner.tsx 
b/karavan-designer/src/designer/route/RouteDesigner.tsx
index 10a44ca6..67779963 100644
--- a/karavan-designer/src/designer/route/RouteDesigner.tsx
+++ b/karavan-designer/src/designer/route/RouteDesigner.tsx
@@ -57,13 +57,14 @@ interface State {
     parentDsl?: string
     selectedPosition?: number
     showSteps: boolean
-    selectedUuid: string
+    selectedUuids: string []
     key: string
     width: number
     height: number
     top: number
     left: number
-    clipboardStep?: CamelElement
+    clipboardSteps: CamelElement[]
+    shiftKeyPressed?: boolean
     ref?: any
     printerRef?: any
     propertyOnly: boolean
@@ -79,7 +80,8 @@ export class RouteDesigner extends React.Component<Props, 
State> {
         deleteMessage: '',
         parentId: '',
         showSteps: true,
-        selectedUuid: '',
+        selectedUuids: [],
+        clipboardSteps: [],
         key: "",
         width: 1000,
         height: 1000,
@@ -93,6 +95,7 @@ export class RouteDesigner extends React.Component<Props, 
State> {
     componentDidMount() {
         window.addEventListener('resize', this.handleResize);
         window.addEventListener('keydown', this.handleKeyDown);
+        window.addEventListener('keyup', this.handleKeyUp);
         const element = 
findDOMNode(this.state.ref.current)?.parentElement?.parentElement;
         const checkResize = (mutations: any) => {
             const el = mutations[0].target;
@@ -109,6 +112,7 @@ export class RouteDesigner extends React.Component<Props, 
State> {
     componentWillUnmount() {
         window.removeEventListener('resize', this.handleResize);
         window.removeEventListener('keydown', this.handleKeyDown);
+        window.removeEventListener('keyup', this.handleKeyUp);
     }
 
     handleResize = (event: any) => {
@@ -116,24 +120,35 @@ export class RouteDesigner extends React.Component<Props, 
State> {
     }
 
     handleKeyDown = (event: KeyboardEvent) => {
-        const {integration, selectedUuid, clipboardStep} = this.state;
-        if (window.document.hasFocus() && window.document.activeElement && 
selectedUuid && selectedUuid !== '') {
+        const {integration, selectedUuids, clipboardSteps} = this.state;
+        if ((event.shiftKey)) {
+            this.setState({shiftKeyPressed: true});
+        }
+        if (window.document.hasFocus() && window.document.activeElement) {
             if (['BODY', 
'MAIN'].includes(window.document.activeElement.tagName)) {
                 let charCode = String.fromCharCode(event.which).toLowerCase();
                 if ((event.ctrlKey || event.metaKey) && charCode === 'c') {
-                    const selectedElement = 
CamelDefinitionApiExt.findElementInIntegration(integration, selectedUuid);
-                    this.saveToClipboard(selectedElement);
+                    const steps: CamelElement[] = []
+                    selectedUuids.forEach(selectedUuid => {
+                        const selectedElement = 
CamelDefinitionApiExt.findElementInIntegration(integration, selectedUuid);
+                        if (selectedElement) {
+                            steps.push(selectedElement);
+                        }
+                    })
+                    this.saveToClipboard(steps);
                 } else if ((event.ctrlKey || event.metaKey) && charCode === 
'v') {
-                    if (clipboardStep?.dslName === 'FromDefinition') {
-                        const clone = CamelUtil.cloneStep(clipboardStep, true);
+                    if (clipboardSteps.length === 1 && 
clipboardSteps[0]?.dslName === 'FromDefinition') {
+                        const clone = CamelUtil.cloneStep(clipboardSteps[0], 
true);
                         const route = 
CamelDefinitionApi.createRouteDefinition({from: clone});
                         this.addStep(route, '', 0)
-                    } else {
-                        const meta = 
CamelDefinitionApiExt.findElementMetaInIntegration(integration, selectedUuid);
-                        if (clipboardStep && meta.parentUuid) {
-                            const clone = CamelUtil.cloneStep(clipboardStep, 
true);
-                            this.addStep(clone, meta.parentUuid, 
meta.position);
-                        }
+                    } else if (selectedUuids.length === 1) {
+                        const targetMeta = 
CamelDefinitionApiExt.findElementMetaInIntegration(integration, 
selectedUuids[0]);
+                        clipboardSteps.reverse().forEach(clipboardStep => {
+                            if (clipboardStep && targetMeta.parentUuid) {
+                                const clone = 
CamelUtil.cloneStep(clipboardStep, true);
+                                this.addStep(clone, targetMeta.parentUuid, 
targetMeta.position);
+                            }
+                        })
                     }
                 }
             }
@@ -144,14 +159,26 @@ export class RouteDesigner extends React.Component<Props, 
State> {
         }
     }
 
+    handleKeyUp = (event: KeyboardEvent) => {
+        this.setState({shiftKeyPressed: false});
+        if (event.repeat) {
+            window.dispatchEvent(event);
+        }
+    }
+
     componentDidUpdate = (prevProps: Readonly<Props>, prevState: 
Readonly<State>, snapshot?: any) => {
         if (prevState.key !== this.state.key) {
             this.props.onSave?.call(this, this.state.integration, 
this.state.propertyOnly);
         }
     }
 
-    saveToClipboard = (step?: CamelElement): void => {
-        this.setState({clipboardStep: step, key: Math.random().toString()});
+    saveToClipboard = (steps: CamelElement[]): void => {
+        if (steps.length >0) {
+            this.setState(prevState => ({
+                key: Math.random().toString(),
+                clipboardSteps: [...steps]
+            }));
+        }
     }
 
     onPropertyUpdate = (element: CamelElement, newRoute?: RouteToCreate) => {
@@ -161,14 +188,14 @@ export class RouteDesigner extends React.Component<Props, 
State> {
             const r = CamelDefinitionApi.createRouteDefinition({from: f, id: 
newRoute.name})
             i = CamelDefinitionApiExt.addStepToIntegration(i, r, '');
             const clone = CamelUtil.cloneIntegration(i);
-            this.setState({
+            this.setState(prevState => ({
                 integration: clone,
                 key: Math.random().toString(),
                 showSelector: false,
                 selectedStep: element,
-                selectedUuid: element.uuid,
-                propertyOnly: false
-            });
+                propertyOnly: false,
+                selectedUuids: [element.uuid]
+            }));
         } else {
             const clone = CamelUtil.cloneIntegration(this.state.integration);
             const i = 
CamelDefinitionApiExt.updateIntegrationRouteElement(clone, element);
@@ -201,37 +228,73 @@ export class RouteDesigner extends React.Component<Props, 
State> {
         } else {
             message = 'Delete element from route?';
         }
-        this.setState({selectedUuid: id, showSelector: false, 
showDeleteConfirmation: true, deleteMessage: message});
+        this.setState(prevState => ({
+            showSelector: false,
+            showDeleteConfirmation: true,
+            deleteMessage: message,
+            selectedUuids: [id],
+        }));
     }
 
     deleteElement = () => {
-        const id = this.state.selectedUuid;
-        const i = 
CamelDefinitionApiExt.deleteStepFromIntegration(this.state.integration, id);
-        this.setState({
-            integration: i,
-            showSelector: false,
-            showDeleteConfirmation: false,
-            deleteMessage: '',
-            key: Math.random().toString(),
-            selectedStep: undefined,
-            selectedUuid: '',
-            propertyOnly: false
-        });
-        const el = new CamelElement("");
-        el.uuid = id;
-        EventBus.sendPosition("delete", el, undefined, new DOMRect(), new 
DOMRect(), 0);
+        const id = this.state.selectedUuids.at(0);
+        if (id) {
+            const i = 
CamelDefinitionApiExt.deleteStepFromIntegration(this.state.integration, id);
+            this.setState(prevState => ({
+                integration: i,
+                showSelector: false,
+                showDeleteConfirmation: false,
+                deleteMessage: '',
+                key: Math.random().toString(),
+                selectedStep: undefined,
+                propertyOnly: false,
+                selectedUuids: [id],
+            }));
+            const el = new CamelElement("");
+            el.uuid = id;
+            EventBus.sendPosition("delete", el, undefined, new DOMRect(), new 
DOMRect(), 0);
+        }
     }
 
     selectElement = (element: CamelElement) => {
+        const {shiftKeyPressed, selectedUuids, integration} = this.state;
+        let canNotAdd: boolean = false;
+        if (shiftKeyPressed) {
+            const hasFrom = selectedUuids.map(e => 
CamelDefinitionApiExt.findElementInIntegration(integration, e)?.dslName === 
'FromDefinition').filter(r => r).length > 0;
+            canNotAdd = hasFrom || (selectedUuids.length > 0 && 
element.dslName === 'FromDefinition');
+        }
+        const add = shiftKeyPressed && !selectedUuids.includes(element.uuid);
+        const remove = shiftKeyPressed && selectedUuids.includes(element.uuid);
         const i = 
CamelDisplayUtil.setIntegrationVisibility(this.state.integration, element.uuid);
-        this.setState({integration: i, selectedStep: element, selectedUuid: 
element.uuid, showSelector: false})
+        this.setState((prevState: State) => {
+            if (remove) {
+                const index = prevState.selectedUuids.indexOf(element.uuid);
+                prevState.selectedUuids.splice(index, 1);
+            } else if (add && !canNotAdd) {
+                prevState.selectedUuids.push(element.uuid);
+            }
+            const uuid: string = 
prevState.selectedUuids.includes(element.uuid) ? element.uuid : 
prevState.selectedUuids.at(0) || '';
+            const selectedElement = shiftKeyPressed ? 
CamelDefinitionApiExt.findElementInIntegration(integration, uuid) : element;
+            return {
+                integration: i,
+                selectedStep: selectedElement,
+                showSelector: false,
+                selectedUuids: shiftKeyPressed ? [...prevState.selectedUuids] 
: [element.uuid],
+            }
+        });
     }
 
     unselectElement = (evt: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
         if ((evt.target as any).dataset.click === 'FLOWS') {
             evt.stopPropagation()
             const i = 
CamelDisplayUtil.setIntegrationVisibility(this.state.integration, undefined);
-            this.setState({integration: i, selectedStep: undefined, 
selectedUuid: '', showSelector: false, selectedPosition: undefined})
+            this.setState(prevState => ({
+                integration: i,
+                selectedStep: undefined,
+                showSelector: false,
+                selectedPosition: undefined,
+                selectedUuids: [],
+            }));
         }
     }
 
@@ -279,27 +342,27 @@ export class RouteDesigner extends React.Component<Props, 
State> {
         const clone = CamelUtil.cloneIntegration(this.state.integration);
         const routeConfiguration = new RouteConfigurationDefinition();
         const i = 
CamelDefinitionApiExt.addRouteConfigurationToIntegration(clone, 
routeConfiguration);
-        this.setState({
+        this.setState(prevState => ({
             integration: i,
             propertyOnly: false,
             key: Math.random().toString(),
             selectedStep: routeConfiguration,
-            selectedUuid: routeConfiguration.uuid,
-        });
+            selectedUuids: [routeConfiguration.uuid],
+        }));
     }
 
     addStep = (step: CamelElement, parentId: string, position?: number | 
undefined) => {
         const i = 
CamelDefinitionApiExt.addStepToIntegration(this.state.integration, step, 
parentId, position);
         const clone = CamelUtil.cloneIntegration(i);
         EventBus.sendPosition("clean", step, undefined, new DOMRect(), new 
DOMRect(), 0);
-        this.setState({
+        this.setState(prevState => ({
             integration: clone,
             key: Math.random().toString(),
             showSelector: false,
             selectedStep: step,
-            selectedUuid: step.uuid,
-            propertyOnly: false
-        });
+            propertyOnly: false,
+            selectedUuids: [step.uuid],
+        }));
     }
 
     onIntegrationUpdate = (i: Integration) => {
@@ -310,14 +373,14 @@ export class RouteDesigner extends React.Component<Props, 
State> {
         const i = 
CamelDefinitionApiExt.moveRouteElement(this.state.integration, source, target, 
asChild);
         const clone = CamelUtil.cloneIntegration(i);
         const selectedStep = 
CamelDefinitionApiExt.findElementInIntegration(clone, source);
-        this.setState({
+        this.setState(prevState => ({
             integration: clone,
             key: Math.random().toString(),
             showSelector: false,
             selectedStep: selectedStep,
-            selectedUuid: source,
-            propertyOnly: false
-        });
+            propertyOnly: false,
+            selectedUuids: [source],
+        }));
     }
 
     onResizePage(el: HTMLDivElement | null) {
@@ -406,7 +469,7 @@ export class RouteDesigner extends React.Component<Props, 
State> {
     }
 
     getGraph() {
-        const {selectedUuid, integration, key, width, height, top, left} = 
this.state;
+        const {selectedUuids, integration, key, width, height, top, left} = 
this.state;
         const routes = CamelUi.getRoutes(integration);
         const routeConfigurations = 
CamelUi.getRouteConfigurations(integration);
         return (
@@ -421,7 +484,7 @@ export class RouteDesigner extends React.Component<Props, 
State> {
                                     deleteElement={this.showDeleteConfirmation}
                                     selectElement={this.selectElement}
                                     moveElement={this.moveElement}
-                                    selectedUuid={selectedUuid}
+                                    selectedUuid={selectedUuids}
                                     inSteps={false}
                                     position={index}
                                     step={routeConfiguration}
@@ -434,7 +497,7 @@ export class RouteDesigner extends React.Component<Props, 
State> {
                                     deleteElement={this.showDeleteConfirmation}
                                     selectElement={this.selectElement}
                                     moveElement={this.moveElement}
-                                    selectedUuid={selectedUuid}
+                                    selectedUuid={selectedUuids}
                                     inSteps={false}
                                     position={index}
                                     step={route}

Reply via email to