| /* |
| * 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 '../karavan.css'; |
| import {DslMetaModel} from "../utils/DslMetaModel"; |
| import {CamelUtil} from "karavan-core/lib/api/CamelUtil"; |
| import {ChoiceDefinition, FromDefinition, LogDefinition, RouteConfigurationDefinition, RouteDefinition} from "karavan-core/lib/model/CamelDefinition"; |
| import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition"; |
| import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt"; |
| import {CamelDefinitionApi} from "karavan-core/lib/api/CamelDefinitionApi"; |
| import {Command, EventBus} from "../utils/EventBus"; |
| import {CamelDisplayUtil} from "karavan-core/lib/api/CamelDisplayUtil"; |
| import {toPng} from 'html-to-image'; |
| import {useDesignerStore, useIntegrationStore, useSelectorStore} from "../KaravanStore"; |
| import {shallow} from "zustand/shallow"; |
| |
| export function useRouteDesignerHook () { |
| |
| const [integration, setIntegration] = useIntegrationStore((state) => [state.integration, state.setIntegration], shallow) |
| const [selectedUuids,clipboardSteps,shiftKeyPressed, |
| setShowDeleteConfirmation, setDeleteMessage, setSelectedStep, setSelectedUuids, setClipboardSteps, setShiftKeyPressed, |
| width, height, dark, hideLogDSL] = useDesignerStore((s) => |
| [s.selectedUuids,s.clipboardSteps, s.shiftKeyPressed, |
| s.setShowDeleteConfirmation, s.setDeleteMessage, s.setSelectedStep, s.setSelectedUuids, s.setClipboardSteps, s.setShiftKeyPressed, |
| s.width, s.height, s.dark, s.hideLogDSL], shallow) |
| const [setParentId, setShowSelector, setSelectorTabIndex, setParentDsl, setShowSteps, setSelectedPosition] = useSelectorStore((s) => |
| [s.setParentId, s.setShowSelector, s.setSelectorTabIndex, s.setParentDsl, s.setShowSteps, s.setSelectedPosition], shallow) |
| |
| function onCommand (command: Command, printerRef: React.MutableRefObject<HTMLDivElement | null>) { |
| switch (command.command){ |
| case "downloadImage": integrationImageDownload(printerRef); |
| } |
| } |
| |
| const onShowDeleteConfirmation = (id: string) => { |
| let message: string; |
| const uuidsToDelete:string [] = [id]; |
| let ce: CamelElement; |
| ce = CamelDefinitionApiExt.findElementInIntegration(integration, id)!; |
| if (ce.dslName === 'FromDefinition') { // Get the RouteDefinition for this.routeDesigner. Use its uuid. |
| let flows = integration.spec.flows!; |
| for (let i = 0; i < flows.length; i++) { |
| if (flows[i].dslName === 'RouteDefinition') { |
| let routeDefinition: RouteDefinition = flows[i]; |
| if (routeDefinition.from.uuid === id) { |
| uuidsToDelete.push(routeDefinition.uuid); |
| break; |
| } |
| } |
| } |
| message = 'Deleting the first element will delete the entire route!'; |
| } else if (ce.dslName === 'RouteDefinition') { |
| message = 'Delete route?'; |
| } else if (ce.dslName === 'RouteConfigurationDefinition') { |
| message = 'Delete route configuration?'; |
| } else { |
| message = 'Delete element from route?'; |
| } |
| setShowDeleteConfirmation(true); |
| setDeleteMessage(message); |
| setSelectedUuids(uuidsToDelete); |
| } |
| |
| const deleteElement = () => { |
| selectedUuids.forEach(uuidToDelete => { |
| const i = CamelDefinitionApiExt.deleteStepFromIntegration(integration, uuidToDelete); |
| setIntegration(i, false); |
| setShowSelector(false); |
| setShowDeleteConfirmation(false); |
| setDeleteMessage(''); |
| setSelectedStep(undefined); |
| setSelectedUuids([uuidToDelete]); |
| |
| const el = new CamelElement(""); |
| el.uuid = uuidToDelete; |
| EventBus.sendPosition("delete", el, undefined, new DOMRect(), new DOMRect(), 0); |
| }); |
| } |
| |
| const selectElement = (element: CamelElement) => { |
| const uuids = [...selectedUuids]; |
| let canNotAdd: boolean = false; |
| if (shiftKeyPressed) { |
| const hasFrom = uuids.map(e => CamelDefinitionApiExt.findElementInIntegration(integration, e)?.dslName === 'FromDefinition').filter(r => r).length > 0; |
| canNotAdd = hasFrom || (uuids.length > 0 && element.dslName === 'FromDefinition'); |
| } |
| const add = shiftKeyPressed && !uuids.includes(element.uuid); |
| const remove = shiftKeyPressed && uuids.includes(element.uuid); |
| // TODO: do we need to change Integration just for select???? |
| const i = CamelDisplayUtil.setIntegrationVisibility(integration, element.uuid); |
| |
| if (remove) { |
| const index = uuids.indexOf(element.uuid); |
| uuids.splice(index, 1); |
| } else if (add && !canNotAdd) { |
| uuids.push(element.uuid); |
| } |
| const uuid: string = uuids.includes(element.uuid) ? element.uuid : uuids.at(0) || ''; |
| const selectedElement = shiftKeyPressed ? CamelDefinitionApiExt.findElementInIntegration(integration, uuid) : element; |
| |
| setIntegration(i, true); |
| setSelectedStep(selectedElement); |
| setSelectedUuids(shiftKeyPressed ? [...uuids] : [element.uuid]) |
| } |
| |
| function handleKeyDown (event: KeyboardEvent) { |
| if ((event.shiftKey)) { |
| setShiftKeyPressed(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') { |
| copyToClipboard(); |
| } else if ((event.ctrlKey || event.metaKey) && charCode === 'v') { |
| pasteFromClipboard(); |
| } |
| } |
| } else { |
| if (event.repeat) { |
| window.dispatchEvent(event); |
| } |
| } |
| } |
| |
| function handleKeyUp (event: KeyboardEvent) { |
| setShiftKeyPressed(false); |
| if (event.repeat) { |
| window.dispatchEvent(event); |
| } |
| } |
| |
| function copyToClipboard (): void { |
| const steps: CamelElement[] = [] |
| selectedUuids.forEach(selectedUuid => { |
| const selectedElement = CamelDefinitionApiExt.findElementInIntegration(integration, selectedUuid); |
| if (selectedElement) { |
| steps.push(selectedElement); |
| } |
| }) |
| if (steps.length > 0) { |
| setClipboardSteps(steps); |
| } |
| } |
| function pasteFromClipboard (): void { |
| if (clipboardSteps.length === 1 && clipboardSteps[0]?.dslName === 'FromDefinition') { |
| const clone = CamelUtil.cloneStep(clipboardSteps[0], true); |
| const route = CamelDefinitionApi.createRouteDefinition({from: clone}); |
| addStep(route, '', 0) |
| } else if (clipboardSteps.length === 1 && clipboardSteps[0]?.dslName === 'RouteDefinition') { |
| const clone = CamelUtil.cloneStep(clipboardSteps[0], true); |
| addStep(clone, '', 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); |
| addStep(clone, targetMeta.parentUuid, targetMeta.position); |
| } |
| }) |
| } |
| } |
| |
| function unselectElement (evt: React.MouseEvent<HTMLDivElement, MouseEvent>) { |
| if ((evt.target as any).dataset.click === 'FLOWS') { |
| evt.stopPropagation() |
| const i = CamelDisplayUtil.setIntegrationVisibility(integration, undefined); |
| setIntegration(i, true); |
| setSelectedStep(undefined); |
| setSelectedPosition(undefined); |
| setSelectedUuids([]); |
| } |
| } |
| |
| const openSelector = (parentId: string | undefined, parentDsl: string | undefined, showSteps: boolean = true, position?: number | undefined, selectorTabIndex?: string | number) => { |
| setShowSelector(true); |
| setParentId(parentId || ''); |
| setParentDsl(parentDsl); |
| setShowSteps(showSteps); |
| setSelectedPosition(position); |
| setSelectorTabIndex((parentId === undefined && parentDsl === undefined) ? 'kamelet' : 'eip'); |
| } |
| |
| const onDslSelect = (dsl: DslMetaModel, parentId: string, position?: number | undefined) => { |
| switch (dsl.dsl) { |
| case 'FromDefinition' : |
| const route = CamelDefinitionApi.createRouteDefinition({from: new FromDefinition({uri: dsl.uri})}); |
| addStep(route, parentId, position) |
| break; |
| case 'ToDefinition' : |
| const to = CamelDefinitionApi.createStep(dsl.dsl, {uri: dsl.uri}); |
| addStep(to, parentId, position) |
| break; |
| case 'ToDynamicDefinition' : |
| const toD = CamelDefinitionApi.createStep(dsl.dsl, {uri: dsl.uri}); |
| addStep(toD, parentId, position) |
| break; |
| case 'KameletDefinition' : |
| const kamelet = CamelDefinitionApi.createStep(dsl.dsl, {name: dsl.name}); |
| addStep(kamelet, parentId, position) |
| break; |
| default: |
| const step = CamelDefinitionApi.createStep(dsl.dsl, undefined); |
| const augmentedStep = setDslDefaults(step); |
| addStep(augmentedStep, parentId, position) |
| break; |
| } |
| } |
| |
| function setDslDefaults(step: CamelElement): CamelElement { |
| if (step.dslName === 'LogDefinition') { |
| // eslint-disable-next-line no-template-curly-in-string |
| (step as LogDefinition).message = "${body}"; |
| } |
| if (step.dslName === 'ChoiceDefinition') { |
| (step as ChoiceDefinition).when?.push(CamelDefinitionApi.createStep('WhenDefinition', undefined)); |
| (step as ChoiceDefinition).otherwise = CamelDefinitionApi.createStep('OtherwiseDefinition', undefined); |
| } |
| return step; |
| } |
| |
| const createRouteConfiguration = () => { |
| const clone = CamelUtil.cloneIntegration(integration); |
| const routeConfiguration = new RouteConfigurationDefinition(); |
| const i = CamelDefinitionApiExt.addRouteConfigurationToIntegration(clone, routeConfiguration); |
| setIntegration(i, false); |
| setSelectedStep(routeConfiguration); |
| setSelectedUuids([routeConfiguration.uuid]); |
| } |
| |
| const addStep = (step: CamelElement, parentId: string, position?: number | undefined) => { |
| const clone = CamelUtil.cloneIntegration(integration); |
| const i = CamelDefinitionApiExt.addStepToIntegration(clone, step, parentId, position); |
| const selectedStep = step.dslName === 'RouteDefinition' ? (step as RouteDefinition).from : step; |
| setIntegration(i, false); |
| setSelectedStep(selectedStep); |
| setSelectedUuids([selectedStep.uuid]); |
| } |
| |
| |
| const moveElement = (source: string, target: string, asChild: boolean) => { |
| const i = CamelDefinitionApiExt.moveRouteElement(integration, source, target, asChild); |
| const clone = CamelUtil.cloneIntegration(i); |
| const selectedStep = CamelDefinitionApiExt.findElementInIntegration(clone, source); |
| |
| setIntegration(clone, false); |
| setShowSelector(false); |
| setSelectedStep(selectedStep); |
| setSelectedUuids([source]); |
| } |
| |
| function downloadIntegrationImage(dataUrl: string) { |
| const a = document.createElement('a'); |
| a.setAttribute('download', 'karavan-routes.png'); |
| a.setAttribute('href', dataUrl); |
| a.click(); |
| } |
| |
| function integrationImageDownloadFilter (node: HTMLElement) { |
| const exclusionClasses = ['add-flow']; |
| return !exclusionClasses.some(classname => { |
| return node.classList === undefined ? false : node.classList.contains(classname); |
| }); |
| } |
| |
| function integrationImageDownload(printerRef: React.MutableRefObject<HTMLDivElement | null>) { |
| const ref = printerRef.current; |
| if (ref !== null) { |
| toPng(ref, { |
| style: {overflow: 'hidden'}, cacheBust: true, filter: integrationImageDownloadFilter, |
| height: height, width: width, backgroundColor: dark ? "black" : "white" |
| }).then(v => { |
| toPng(ref, { |
| style: {overflow: 'hidden'}, cacheBust: true, filter: integrationImageDownloadFilter, |
| height: height, width: width, backgroundColor: dark ? "black" : "white" |
| }).then(downloadIntegrationImage); |
| }) |
| } |
| } |
| |
| return { deleteElement, selectElement, moveElement, onShowDeleteConfirmation, onDslSelect, openSelector, |
| createRouteConfiguration, onCommand, handleKeyDown, handleKeyUp, unselectElement} |
| } |