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 41d6643d Apply last changes to space 41d6643d is described below commit 41d6643dd6a4d8c4bd272df70e554be2e98d6e08 Author: Marat Gubaidullin <marat.gubaidul...@gmail.com> AuthorDate: Mon Jan 23 09:49:14 2023 -0500 Apply last changes to space --- karavan-space/src/designer/rest/RestDesigner.tsx | 1 - .../src/designer/route/DslConnections.tsx | 8 +- karavan-space/src/designer/route/DslElement.tsx | 23 ++- karavan-space/src/designer/route/DslProperties.tsx | 19 -- karavan-space/src/designer/route/RouteDesigner.tsx | 201 +++++++++++++++------ karavan-space/src/designer/utils/EventBus.ts | 6 +- 6 files changed, 170 insertions(+), 88 deletions(-) diff --git a/karavan-space/src/designer/rest/RestDesigner.tsx b/karavan-space/src/designer/rest/RestDesigner.tsx index f91c5129..d13d07e6 100644 --- a/karavan-space/src/designer/rest/RestDesigner.tsx +++ b/karavan-space/src/designer/rest/RestDesigner.tsx @@ -240,7 +240,6 @@ export class RestDesigner extends React.Component<Props, State> { step={this.state.selectedStep} onIntegrationUpdate={this.onIntegrationUpdate} onPropertyUpdate={this.onPropertyUpdate} - clipboardStep={undefined} isRouteDesigner={false} onClone={this.cloneRest} dark={this.props.dark}/> diff --git a/karavan-space/src/designer/route/DslConnections.tsx b/karavan-space/src/designer/route/DslConnections.tsx index 0806761e..6dc1ced3 100644 --- a/karavan-space/src/designer/route/DslConnections.tsx +++ b/karavan-space/src/designer/route/DslConnections.tsx @@ -57,12 +57,18 @@ export class DslConnections extends React.Component<Props, State> { } setPosition(evt: DslPosition) { - if (evt.command === "add") this.setState(prevState => ({steps: prevState.steps.set(evt.step.uuid, evt)})); + if (evt.command === "add") { + this.setState(prevState => ({steps: prevState.steps.set(evt.step.uuid, evt)})); + } else if (evt.command === "delete") this.setState(prevState => { // prevState.steps.clear(); prevState.steps.delete(evt.step.uuid); return {steps: prevState.steps}; }); + else if (evt.command === "clean") this.setState(prevState => { + prevState.steps.clear(); + return {steps: prevState.steps}; + }); } getIncomings() { diff --git a/karavan-space/src/designer/route/DslElement.tsx b/karavan-space/src/designer/route/DslElement.tsx index 868f3e2c..f9cd9498 100644 --- a/karavan-space/src/designer/route/DslElement.tsx +++ b/karavan-space/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,16 +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 + isDraggedOver: false, }; - 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(); @@ -126,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 => { @@ -352,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} @@ -373,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"} diff --git a/karavan-space/src/designer/route/DslProperties.tsx b/karavan-space/src/designer/route/DslProperties.tsx index 0359bb36..f0000bc1 100644 --- a/karavan-space/src/designer/route/DslProperties.tsx +++ b/karavan-space/src/designer/route/DslProperties.tsx @@ -36,8 +36,6 @@ import {CamelUtil} from "karavan-core/lib/api/CamelUtil"; import {CamelUi, RouteToCreate} from "../utils/CamelUi"; import {CamelMetadataApi, PropertyMeta} from "karavan-core/lib/model/CamelMetadata"; import {IntegrationHeader} from "../utils/KaravanComponents"; -import CopyIcon from '@patternfly/react-icons/dist/esm/icons/copy-icon' -import PasteIcon from '@patternfly/react-icons/dist/esm/icons/paste-icon' import CloneIcon from "@patternfly/react-icons/dist/esm/icons/clone-icon"; interface Props { @@ -45,8 +43,6 @@ interface Props { step?: CamelElement, onIntegrationUpdate?: any, onPropertyUpdate?: (element: CamelElement, newRoute?: RouteToCreate) => void - clipboardStep?: CamelElement - onSaveClipboardStep?: (element?: CamelElement) => void onClone?: (element: CamelElement) => void isRouteDesigner: boolean dark: boolean @@ -76,15 +72,6 @@ export class DslProperties extends React.Component<Props, State> { } } - pasteClipboardStep = () => { - if (this.props.clipboardStep && this.state.step) { - const clone = CamelUtil.cloneStep(this.props.clipboardStep); - clone.uuid = this.state.step.uuid; - this.setStep(clone) - this.props.onPropertyUpdate?.call(this, clone); - } - } - dataFormatChanged = (value: DataFormatDefinition) => { value.uuid = this.state.step?.uuid ? this.state.step?.uuid : value.uuid; this.setStep(value); @@ -144,12 +131,6 @@ export class DslProperties extends React.Component<Props, State> { <div className="headers"> <div className="top"> <Title headingLevel="h1" size="md">{title}</Title> - <Tooltip content="Copy step" position="bottom"> - <Button variant="link" onClick={() => this.props.onSaveClipboardStep?.call(this, this.state.step)} icon={<CopyIcon/>}/> - </Tooltip> - <Tooltip content="Paste step" position="bottom"> - <Button variant="link" onClick={() => this.pasteClipboardStep()} icon={<PasteIcon/>}/> - </Tooltip> </div> <Text component={TextVariants.p}>{descriptionLines.at(0)}</Text> {descriptionLines.length > 1 && <ExpandableSection toggleText={isDescriptionExpanded ? 'Show less' : 'Show more'} diff --git a/karavan-space/src/designer/route/RouteDesigner.tsx b/karavan-space/src/designer/route/RouteDesigner.tsx index 949295ea..67779963 100644 --- a/karavan-space/src/designer/route/RouteDesigner.tsx +++ b/karavan-space/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, @@ -92,6 +94,8 @@ 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; @@ -107,20 +111,74 @@ 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) => { this.setState({key: Math.random().toString()}); } + handleKeyDown = (event: KeyboardEvent) => { + 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 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 (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 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); + } + }) + } + } + } + } else { + if (event.repeat) { + window.dispatchEvent(event); + } + } + } + + 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) => { @@ -130,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); @@ -170,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: [], + })); } } @@ -222,8 +316,8 @@ export class RouteDesigner extends React.Component<Props, State> { onDslSelect = (dsl: DslMetaModel, parentId: string, position?: number | undefined) => { switch (dsl.dsl) { case 'FromDefinition' : - const from = CamelDefinitionApi.createRouteDefinition({from: new FromDefinition({uri: dsl.uri})}); - this.addStep(from, parentId, position) + const route = CamelDefinitionApi.createRouteDefinition({from: new FromDefinition({uri: dsl.uri})}); + this.addStep(route, parentId, position) break; case 'ToDefinition' : const to = CamelDefinitionApi.createStep(dsl.dsl, {uri: dsl.uri}); @@ -248,26 +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); - this.setState({ + EventBus.sendPosition("clean", step, undefined, new DOMRect(), new DOMRect(), 0); + 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) => { @@ -278,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) { @@ -338,9 +433,7 @@ export class RouteDesigner extends React.Component<Props, State> { step={this.state.selectedStep} onIntegrationUpdate={this.onIntegrationUpdate} onPropertyUpdate={this.onPropertyUpdate} - clipboardStep={this.state.clipboardStep} isRouteDesigner={true} - onSaveClipboardStep={this.saveToClipboard} dark={this.props.dark}/> </DrawerPanelContent> ) @@ -356,7 +449,7 @@ export class RouteDesigner extends React.Component<Props, State> { integrationImageDownloadFilter = (node: HTMLElement) => { const exclusionClasses = ['add-flow']; return !exclusionClasses.some(classname => { - return node.classList === undefined ? false: node.classList.contains(classname); + return node.classList === undefined ? false : node.classList.contains(classname); }); } @@ -364,15 +457,19 @@ export class RouteDesigner extends React.Component<Props, State> { if (this.state.printerRef.current === null) { return } - toPng(this.state.printerRef.current, { style:{overflow:'hidden'}, cacheBust: true, filter: this.integrationImageDownloadFilter, - height:this.state.height,width:this.state.width, backgroundColor: this.props.dark?"black":"white" }).then(v => { - toPng(this.state.printerRef.current, { style:{overflow:'hidden'}, cacheBust: true, filter: this.integrationImageDownloadFilter, - height:this.state.height,width:this.state.width, backgroundColor: this.props.dark?"black":"white" }).then(this.downloadIntegrationImage); - }) + toPng(this.state.printerRef.current, { + style: {overflow: 'hidden'}, cacheBust: true, filter: this.integrationImageDownloadFilter, + height: this.state.height, width: this.state.width, backgroundColor: this.props.dark ? "black" : "white" + }).then(v => { + toPng(this.state.printerRef.current, { + style: {overflow: 'hidden'}, cacheBust: true, filter: this.integrationImageDownloadFilter, + height: this.state.height, width: this.state.width, backgroundColor: this.props.dark ? "black" : "white" + }).then(this.downloadIntegrationImage); + }) } 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 ( @@ -380,14 +477,14 @@ export class RouteDesigner extends React.Component<Props, State> { <DslConnections height={height} width={width} top={top} left={left} integration={integration}/> <div className="flows" data-click="FLOWS" onClick={event => this.unselectElement(event)} ref={el => this.onResizePage(el)}> - {routeConfigurations?.map((routeConfiguration , index: number) => ( + {routeConfigurations?.map((routeConfiguration, index: number) => ( <DslElement key={routeConfiguration.uuid + key} integration={integration} openSelector={this.openSelector} deleteElement={this.showDeleteConfirmation} selectElement={this.selectElement} moveElement={this.moveElement} - selectedUuid={selectedUuid} + selectedUuid={selectedUuids} inSteps={false} position={index} step={routeConfiguration} @@ -400,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} diff --git a/karavan-space/src/designer/utils/EventBus.ts b/karavan-space/src/designer/utils/EventBus.ts index 98e573d7..2df867f4 100644 --- a/karavan-space/src/designer/utils/EventBus.ts +++ b/karavan-space/src/designer/utils/EventBus.ts @@ -27,9 +27,9 @@ export class DslPosition { position: number = 0; rect: DOMRect = new DOMRect(); headerRect: DOMRect = new DOMRect(); - command: "add" | "delete" = "add"; + command: "add" | "delete" | "clean" = "add"; - constructor(command: "add" | "delete", + constructor(command: "add" | "delete" | "clean", step: CamelElement, parent:CamelElement | undefined, rect: DOMRect, @@ -49,7 +49,7 @@ export class DslPosition { } export const EventBus = { - sendPosition: (command: "add" | "delete", + sendPosition: (command: "add" | "delete" | "clean", step: CamelElement, parent: CamelElement | undefined, rect: DOMRect,