diff --git a/packages/ui/src/components/Visualization/Custom/CustomGroup.tsx b/packages/ui/src/components/Visualization/Custom/CustomGroup.tsx index 38d53ea9c..460ad25f3 100644 --- a/packages/ui/src/components/Visualization/Custom/CustomGroup.tsx +++ b/packages/ui/src/components/Visualization/Custom/CustomGroup.tsx @@ -1,5 +1,8 @@ +import { CodeBranchIcon } from '@patternfly/react-icons'; import { + ContextMenuSeparator, DefaultGroup, + ElementModel, GraphElement, Layer, isNode, @@ -7,11 +10,11 @@ import { withContextMenu, withSelection, } from '@patternfly/react-topology'; -import { FunctionComponent } from 'react'; +import { FunctionComponent, ReactElement } from 'react'; import { AddStepMode } from '../../../models/visualization/base-visual-entity'; import { CanvasNode } from '../Canvas/canvas.models'; -import { ItemInsertChildNode } from './ItemInsertChildNode'; -import { ItemRemoveGroup } from './ItemRemoveGroup'; +import { ItemDeleteGroup } from './ItemDeleteGroup'; +import { ItemInsertStep } from './ItemInsertStep'; type IDefaultGroup = Parameters[0]; interface ICustomGroup extends IDefaultGroup { @@ -45,11 +48,38 @@ const CustomGroup: FunctionComponent = observer(({ element, ...res ); }); -export const CustomGroupWithSelection = withContextMenu(() => [ - , - , -])(withSelection()(CustomGroup)); +export const CustomGroupWithSelection = withSelection()( + withContextMenu((element: GraphElement) => { + const items: ReactElement[] = []; + const vizNode = element.getData()?.vizNode; + if (!vizNode) return items; + + const nodeInteractions = vizNode.getNodeInteraction(); + + if (nodeInteractions.canHaveSpecialChildren) { + items.push( + + Add branch + , + ); + items.push(); + } + + if (nodeInteractions.canRemoveFlow) { + items.push( + , + ); + } + + return items; + })(CustomGroup), +); diff --git a/packages/ui/src/components/Visualization/Custom/CustomNode.tsx b/packages/ui/src/components/Visualization/Custom/CustomNode.tsx index 56e1d7d70..41b4ae3e8 100644 --- a/packages/ui/src/components/Visualization/Custom/CustomNode.tsx +++ b/packages/ui/src/components/Visualization/Custom/CustomNode.tsx @@ -1,23 +1,27 @@ +import { Tooltip } from '@patternfly/react-core'; +import { ArrowDownIcon, ArrowUpIcon, CodeBranchIcon, PlusIcon } from '@patternfly/react-icons'; import { + ContextMenuSeparator, DefaultNode, + ElementModel, + GraphElement, Node, NodeStatus, + WithSelectionProps, observer, withContextMenu, withSelection, - WithSelectionProps, } from '@patternfly/react-topology'; -import { Tooltip } from '@patternfly/react-core'; -import { FunctionComponent } from 'react'; +import { FunctionComponent, ReactElement } from 'react'; import { AddStepMode } from '../../../models/visualization/base-visual-entity'; import { CanvasDefaults } from '../Canvas/canvas.defaults'; import { CanvasNode } from '../Canvas/canvas.models'; import './CustomNode.scss'; -import { ItemAddNode } from './ItemAddNode'; -import { ItemInsertChildNode } from './ItemInsertChildNode'; -import { ItemRemoveNode } from './ItemRemoveNode'; -import { ItemReplaceNode } from './ItemReplaceNode'; -import { ItemRemoveGroup } from './ItemRemoveGroup'; +import { ItemAddStep } from './ItemAddStep'; +import { ItemDeleteGroup } from './ItemDeleteGroup'; +import { ItemDeleteStep } from './ItemDeleteStep'; +import { ItemInsertStep } from './ItemInsertStep'; +import { ItemReplaceStep } from './ItemReplaceStep'; interface CustomNodeProps extends WithSelectionProps { element: Node; @@ -60,24 +64,92 @@ const CustomNode: FunctionComponent = observer(({ element, ...r ); }); -export const CustomNodeWithSelection: typeof DefaultNode = withContextMenu(() => [ - , - , - , - , - , - , - , -])(withSelection()(CustomNode) as typeof DefaultNode) as typeof DefaultNode; +export const CustomNodeWithSelection: typeof DefaultNode = withSelection()( + withContextMenu((element: GraphElement) => { + const items: ReactElement[] = []; + const vizNode = element.getData()?.vizNode; + if (!vizNode) return items; + + const nodeInteractions = vizNode.getNodeInteraction(); + + if (nodeInteractions.canHavePreviousStep) { + items.push( + + Prepend + , + ); + } + if (nodeInteractions.canHaveNextStep) { + items.push( + + Append + , + ); + } + if (nodeInteractions.canHavePreviousStep || nodeInteractions.canHaveNextStep) { + items.push(); + } + + if (nodeInteractions.canHaveChildren) { + items.push( + + Add step + , + ); + } + if (nodeInteractions.canHaveSpecialChildren) { + items.push( + + Add branch + , + ); + } + if (nodeInteractions.canHaveChildren || nodeInteractions.canHaveSpecialChildren) { + items.push(); + } + + if (nodeInteractions.canReplaceStep) { + items.push( + , + ); + items.push(); + } + + if (nodeInteractions.canRemoveStep) { + items.push( + , + ); + } + if (nodeInteractions.canRemoveFlow) { + items.push( + , + ); + } + + return items; + })(CustomNode as typeof DefaultNode), +) as typeof DefaultNode; diff --git a/packages/ui/src/components/Visualization/Custom/ItemAddNode.tsx b/packages/ui/src/components/Visualization/Custom/ItemAddNode.tsx deleted file mode 100644 index 920d82bda..000000000 --- a/packages/ui/src/components/Visualization/Custom/ItemAddNode.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { AngleDownIcon, AngleUpIcon } from '@patternfly/react-icons'; -import { ContextMenuItem, ElementContext, ElementModel, GraphElement } from '@patternfly/react-topology'; -import { FunctionComponent, useCallback, useContext, useMemo } from 'react'; -import { IDataTestID } from '../../../models'; -import { AddStepMode } from '../../../models/visualization/base-visual-entity'; -import { CatalogModalContext } from '../../../providers/catalog-modal.provider'; -import { EntitiesContext } from '../../../providers/entities.provider'; -import { CanvasNode } from '../Canvas/canvas.models'; - -interface ItemAddNodeProps extends IDataTestID { - mode: AddStepMode.PrependStep | AddStepMode.AppendStep; -} - -export const ItemAddNode: FunctionComponent = (props) => { - const entitiesContext = useContext(EntitiesContext); - const catalogModalContext = useContext(CatalogModalContext); - const element: GraphElement = useContext(ElementContext); - - const vizNode = element.getData()?.vizNode; - const shouldRender = useMemo(() => { - const nodeInteractions = vizNode?.getNodeInteraction() ?? { canHavePreviousStep: false, canHaveNextStep: false }; - - return props.mode === AddStepMode.PrependStep - ? nodeInteractions.canHavePreviousStep - : nodeInteractions.canHaveNextStep; - }, [props.mode, vizNode]); - - const onAddNode = useCallback(async () => { - if (!vizNode || !entitiesContext) return; - - /** Get compatible nodes and the location where can be introduced */ - const compatibleNodes = entitiesContext.camelResource.getCompatibleComponents(props.mode, vizNode.data); - - /** Open Catalog modal, filtering the compatible nodes */ - const definedComponent = await catalogModalContext?.getNewComponent(compatibleNodes); - if (!definedComponent) return; - - /** Add new node to the entities */ - vizNode.addBaseEntityStep(definedComponent, props.mode); - - /** Update entity */ - entitiesContext.updateEntitiesFromCamelResource(); - }, [catalogModalContext, entitiesContext, props.mode, vizNode]); - - return shouldRender ? ( - - {props.mode === AddStepMode.PrependStep ? ( - <> - Prepend - - ) : ( - <> - Append - - )} - - ) : null; -}; diff --git a/packages/ui/src/components/Visualization/Custom/ItemAddStep.tsx b/packages/ui/src/components/Visualization/Custom/ItemAddStep.tsx new file mode 100644 index 000000000..689d2d444 --- /dev/null +++ b/packages/ui/src/components/Visualization/Custom/ItemAddStep.tsx @@ -0,0 +1,39 @@ +import { ContextMenuItem } from '@patternfly/react-topology'; +import { FunctionComponent, PropsWithChildren, useCallback, useContext } from 'react'; +import { IDataTestID } from '../../../models'; +import { AddStepMode, IVisualizationNode } from '../../../models/visualization/base-visual-entity'; +import { CatalogModalContext } from '../../../providers/catalog-modal.provider'; +import { EntitiesContext } from '../../../providers/entities.provider'; + +interface ItemAddStepProps extends PropsWithChildren { + mode: AddStepMode.PrependStep | AddStepMode.AppendStep; + vizNode: IVisualizationNode; +} + +export const ItemAddStep: FunctionComponent = (props) => { + const entitiesContext = useContext(EntitiesContext); + const catalogModalContext = useContext(CatalogModalContext); + + const onAddNode = useCallback(async () => { + if (!props.vizNode || !entitiesContext) return; + + /** Get compatible nodes and the location where can be introduced */ + const compatibleNodes = entitiesContext.camelResource.getCompatibleComponents(props.mode, props.vizNode.data); + + /** Open Catalog modal, filtering the compatible nodes */ + const definedComponent = await catalogModalContext?.getNewComponent(compatibleNodes); + if (!definedComponent) return; + + /** Add new node to the entities */ + props.vizNode.addBaseEntityStep(definedComponent, props.mode); + + /** Update entity */ + entitiesContext.updateEntitiesFromCamelResource(); + }, [catalogModalContext, entitiesContext, props.mode, props.vizNode]); + + return ( + + {props.children} + + ); +}; diff --git a/packages/ui/src/components/Visualization/Custom/ItemDeleteGroup.tsx b/packages/ui/src/components/Visualization/Custom/ItemDeleteGroup.tsx new file mode 100644 index 000000000..d5968f556 --- /dev/null +++ b/packages/ui/src/components/Visualization/Custom/ItemDeleteGroup.tsx @@ -0,0 +1,26 @@ +import { TrashIcon } from '@patternfly/react-icons'; +import { ContextMenuItem } from '@patternfly/react-topology'; +import { FunctionComponent, PropsWithChildren, useCallback, useContext } from 'react'; +import { IDataTestID } from '../../../models'; +import { IVisualizationNode } from '../../../models/visualization/base-visual-entity'; +import { EntitiesContext } from '../../../providers/entities.provider'; + +interface ItemDeleteGroupProps extends PropsWithChildren { + vizNode: IVisualizationNode; +} + +export const ItemDeleteGroup: FunctionComponent = (props) => { + const entitiesContext = useContext(EntitiesContext); + const flowId = props.vizNode?.getBaseEntity()?.getId(); + + const onRemoveGroup = useCallback(() => { + entitiesContext?.camelResource.removeEntity(flowId); + entitiesContext?.updateEntitiesFromCamelResource(); + }, [entitiesContext, flowId]); + + return ( + + Delete + + ); +}; diff --git a/packages/ui/src/components/Visualization/Custom/ItemDeleteStep.tsx b/packages/ui/src/components/Visualization/Custom/ItemDeleteStep.tsx new file mode 100644 index 000000000..6539c6e4d --- /dev/null +++ b/packages/ui/src/components/Visualization/Custom/ItemDeleteStep.tsx @@ -0,0 +1,25 @@ +import { TrashIcon } from '@patternfly/react-icons'; +import { ContextMenuItem } from '@patternfly/react-topology'; +import { FunctionComponent, PropsWithChildren, useCallback, useContext } from 'react'; +import { IDataTestID } from '../../../models'; +import { IVisualizationNode } from '../../../models/visualization/base-visual-entity'; +import { EntitiesContext } from '../../../providers/entities.provider'; + +interface ItemDeleteStepProps extends PropsWithChildren { + vizNode: IVisualizationNode; +} + +export const ItemDeleteStep: FunctionComponent = (props) => { + const entitiesContext = useContext(EntitiesContext); + + const onRemoveNode = useCallback(() => { + props.vizNode?.removeChild(); + entitiesContext?.updateEntitiesFromCamelResource(); + }, [entitiesContext, props.vizNode]); + + return ( + + Delete + + ); +}; diff --git a/packages/ui/src/components/Visualization/Custom/ItemInsertChildNode.tsx b/packages/ui/src/components/Visualization/Custom/ItemInsertChildNode.tsx deleted file mode 100644 index ff1b41d61..000000000 --- a/packages/ui/src/components/Visualization/Custom/ItemInsertChildNode.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { AngleDoubleDownIcon } from '@patternfly/react-icons'; -import { ContextMenuItem, ElementContext, ElementModel, GraphElement } from '@patternfly/react-topology'; -import { FunctionComponent, useCallback, useContext, useMemo } from 'react'; -import { IDataTestID } from '../../../models'; -import { AddStepMode } from '../../../models/visualization/base-visual-entity'; -import { CatalogModalContext } from '../../../providers/catalog-modal.provider'; -import { EntitiesContext } from '../../../providers/entities.provider'; -import { CanvasNode } from '../Canvas/canvas.models'; - -interface ItemInsertChildNodeProps extends IDataTestID { - mode: AddStepMode.InsertChildStep | AddStepMode.InsertSpecialChildStep; -} - -export const ItemInsertChildNode: FunctionComponent = (props) => { - const entitiesContext = useContext(EntitiesContext); - const catalogModalContext = useContext(CatalogModalContext); - const element: GraphElement = useContext(ElementContext); - const vizNode = element.getData()?.vizNode; - const shouldRender = useMemo(() => { - const nodeInteractions = vizNode?.getNodeInteraction() ?? { canHaveChildren: false, canHaveSpecialChildren: false }; - - return props.mode === AddStepMode.InsertChildStep - ? nodeInteractions.canHaveChildren - : nodeInteractions.canHaveSpecialChildren; - }, [props.mode, vizNode]); - - const onInsertNode = useCallback(async () => { - if (!vizNode || !entitiesContext) return; - - /** Get compatible nodes and the location where can be introduced */ - const compatibleNodes = entitiesContext.camelResource.getCompatibleComponents( - props.mode, - vizNode.data, - vizNode.getComponentSchema()?.definition, - ); - - /** Open Catalog modal, filtering the compatible nodes */ - const definedComponent = await catalogModalContext?.getNewComponent(compatibleNodes); - if (!definedComponent) return; - const targetProperty = props.mode === AddStepMode.InsertChildStep ? 'steps' : undefined; - - /** Add new node to the entities */ - vizNode.addBaseEntityStep(definedComponent, props.mode, targetProperty); - - /** Update entity */ - entitiesContext.updateEntitiesFromCamelResource(); - }, [catalogModalContext, entitiesContext, props.mode, vizNode]); - - return shouldRender ? ( - - Insert {props.mode === AddStepMode.InsertSpecialChildStep ? 'special' : ''} step - - ) : null; -}; diff --git a/packages/ui/src/components/Visualization/Custom/ItemInsertStep.tsx b/packages/ui/src/components/Visualization/Custom/ItemInsertStep.tsx new file mode 100644 index 000000000..3fd4fbb0a --- /dev/null +++ b/packages/ui/src/components/Visualization/Custom/ItemInsertStep.tsx @@ -0,0 +1,44 @@ +import { ContextMenuItem } from '@patternfly/react-topology'; +import { FunctionComponent, PropsWithChildren, useCallback, useContext } from 'react'; +import { IDataTestID } from '../../../models'; +import { AddStepMode, IVisualizationNode } from '../../../models/visualization/base-visual-entity'; +import { CatalogModalContext } from '../../../providers/catalog-modal.provider'; +import { EntitiesContext } from '../../../providers/entities.provider'; + +interface ItemInsertStepProps extends PropsWithChildren { + mode: AddStepMode.InsertChildStep | AddStepMode.InsertSpecialChildStep; + vizNode: IVisualizationNode; +} + +export const ItemInsertStep: FunctionComponent = (props) => { + const entitiesContext = useContext(EntitiesContext); + const catalogModalContext = useContext(CatalogModalContext); + + const onInsertNode = useCallback(async () => { + if (!props.vizNode || !entitiesContext) return; + + /** Get compatible nodes and the location where can be introduced */ + const compatibleNodes = entitiesContext.camelResource.getCompatibleComponents( + props.mode, + props.vizNode.data, + props.vizNode.getComponentSchema()?.definition, + ); + + /** Open Catalog modal, filtering the compatible nodes */ + const definedComponent = await catalogModalContext?.getNewComponent(compatibleNodes); + if (!definedComponent) return; + const targetProperty = props.mode === AddStepMode.InsertChildStep ? 'steps' : undefined; + + /** Add new node to the entities */ + props.vizNode.addBaseEntityStep(definedComponent, props.mode, targetProperty); + + /** Update entity */ + entitiesContext.updateEntitiesFromCamelResource(); + }, [catalogModalContext, entitiesContext, props.mode, props.vizNode]); + + return ( + + {props.children} + + ); +}; diff --git a/packages/ui/src/components/Visualization/Custom/ItemRemoveGroup.tsx b/packages/ui/src/components/Visualization/Custom/ItemRemoveGroup.tsx deleted file mode 100644 index e3cca4b28..000000000 --- a/packages/ui/src/components/Visualization/Custom/ItemRemoveGroup.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { TrashIcon } from '@patternfly/react-icons'; -import { ContextMenuItem, ElementContext, ElementModel, GraphElement } from '@patternfly/react-topology'; -import { FunctionComponent, useCallback, useContext, useMemo } from 'react'; -import { IDataTestID } from '../../../models'; -import { EntitiesContext } from '../../../providers/entities.provider'; -import { CanvasNode } from '../Canvas/canvas.models'; - -export const ItemRemoveGroup: FunctionComponent = (props) => { - const entitiesContext = useContext(EntitiesContext); - const element: GraphElement = useContext(ElementContext); - const vizNode = element.getData()?.vizNode; - const flowId = vizNode?.getBaseEntity()?.getId(); - const shouldRender = useMemo(() => { - const nodeInteractions = vizNode?.getNodeInteraction() ?? { canRemoveFlow: false }; - - return nodeInteractions.canRemoveFlow; - }, [vizNode]); - - const onRemoveGroup = useCallback(() => { - entitiesContext?.camelResource.removeEntity(flowId); - entitiesContext?.updateEntitiesFromCamelResource(); - }, [entitiesContext, flowId]); - - return shouldRender ? ( - - Delete - - ) : null; -}; diff --git a/packages/ui/src/components/Visualization/Custom/ItemRemoveNode.tsx b/packages/ui/src/components/Visualization/Custom/ItemRemoveNode.tsx deleted file mode 100644 index 31c6bee86..000000000 --- a/packages/ui/src/components/Visualization/Custom/ItemRemoveNode.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { MinusIcon } from '@patternfly/react-icons'; -import { ContextMenuItem, ElementContext, ElementModel, GraphElement } from '@patternfly/react-topology'; -import { FunctionComponent, useCallback, useContext, useMemo } from 'react'; -import { IDataTestID } from '../../../models'; -import { EntitiesContext } from '../../../providers/entities.provider'; -import { CanvasNode } from '../Canvas/canvas.models'; - -export const ItemRemoveNode: FunctionComponent = (props) => { - const entitiesContext = useContext(EntitiesContext); - const element: GraphElement = useContext(ElementContext); - const vizNode = element.getData()?.vizNode; - const shouldRender = useMemo(() => vizNode?.getNodeInteraction()?.canRemoveStep ?? false, [vizNode]); - - const onRemoveNode = useCallback(() => { - vizNode?.removeChild(); - entitiesContext?.updateEntitiesFromCamelResource(); - }, [entitiesContext, vizNode]); - - return shouldRender ? ( - - Remove - - ) : null; -}; diff --git a/packages/ui/src/components/Visualization/Custom/ItemReplaceNode.tsx b/packages/ui/src/components/Visualization/Custom/ItemReplaceNode.tsx deleted file mode 100644 index efa40bb04..000000000 --- a/packages/ui/src/components/Visualization/Custom/ItemReplaceNode.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { AngleRightIcon } from '@patternfly/react-icons'; -import { ContextMenuItem, ElementContext, ElementModel, GraphElement } from '@patternfly/react-topology'; -import { FunctionComponent, useCallback, useContext, useMemo } from 'react'; -import { IDataTestID } from '../../../models'; -import { AddStepMode } from '../../../models/visualization/base-visual-entity'; -import { CatalogModalContext } from '../../../providers/catalog-modal.provider'; -import { EntitiesContext } from '../../../providers/entities.provider'; -import { CanvasNode } from '../Canvas/canvas.models'; - -export const ItemReplaceNode: FunctionComponent = (props) => { - const entitiesContext = useContext(EntitiesContext); - const catalogModalContext = useContext(CatalogModalContext); - const element: GraphElement = useContext(ElementContext); - const vizNode = element.getData()?.vizNode; - const shouldRender = useMemo(() => vizNode?.getNodeInteraction()?.canReplaceStep ?? false, [vizNode]); - - const onReplaceNode = useCallback(async () => { - if (!vizNode || !entitiesContext) return; - - /** Find compatible components */ - const catalogFilter = entitiesContext.camelResource.getCompatibleComponents(AddStepMode.ReplaceStep, vizNode.data); - - /** Open Catalog modal, filtering the compatible nodes */ - const definedComponent = await catalogModalContext?.getNewComponent(catalogFilter); - if (!definedComponent) return; - - /** Add new node to the entities */ - vizNode.addBaseEntityStep(definedComponent, AddStepMode.ReplaceStep); - - /** Update entity */ - entitiesContext.updateEntitiesFromCamelResource(); - }, [catalogModalContext, entitiesContext, vizNode]); - - return shouldRender ? ( - - Replace - - ) : null; -}; diff --git a/packages/ui/src/components/Visualization/Custom/ItemReplaceStep.tsx b/packages/ui/src/components/Visualization/Custom/ItemReplaceStep.tsx new file mode 100644 index 000000000..18cbc5547 --- /dev/null +++ b/packages/ui/src/components/Visualization/Custom/ItemReplaceStep.tsx @@ -0,0 +1,42 @@ +import { SyncAltIcon } from '@patternfly/react-icons'; +import { ContextMenuItem } from '@patternfly/react-topology'; +import { FunctionComponent, PropsWithChildren, useCallback, useContext } from 'react'; +import { IDataTestID } from '../../../models'; +import { AddStepMode, IVisualizationNode } from '../../../models/visualization/base-visual-entity'; +import { CatalogModalContext } from '../../../providers/catalog-modal.provider'; +import { EntitiesContext } from '../../../providers/entities.provider'; + +interface ItemReplaceStepProps extends PropsWithChildren { + vizNode: IVisualizationNode; +} + +export const ItemReplaceStep: FunctionComponent = (props) => { + const entitiesContext = useContext(EntitiesContext); + const catalogModalContext = useContext(CatalogModalContext); + + const onReplaceNode = useCallback(async () => { + if (!props.vizNode || !entitiesContext) return; + + /** Find compatible components */ + const catalogFilter = entitiesContext.camelResource.getCompatibleComponents( + AddStepMode.ReplaceStep, + props.vizNode.data, + ); + + /** Open Catalog modal, filtering the compatible nodes */ + const definedComponent = await catalogModalContext?.getNewComponent(catalogFilter); + if (!definedComponent) return; + + /** Add new node to the entities */ + props.vizNode.addBaseEntityStep(definedComponent, AddStepMode.ReplaceStep); + + /** Update entity */ + entitiesContext.updateEntitiesFromCamelResource(); + }, [catalogModalContext, entitiesContext, props.vizNode]); + + return ( + + Replace + + ); +};