Skip to content

Commit

Permalink
Feat(DND): Enable Simple DND
Browse files Browse the repository at this point in the history
  • Loading branch information
shivamG640 committed Nov 15, 2024
1 parent 9ee3368 commit b8e6180
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -1,21 +1,33 @@
import { Icon } from '@patternfly/react-core';
import { BanIcon, ExclamationCircleIcon } from '@patternfly/react-icons';
import type { DefaultNode, ElementModel, GraphElement, Node } from '@patternfly/react-topology';
import {
AnchorEnd,
DefaultNode,
DEFAULT_LAYER,
DragObjectWithType,
DragSourceSpec,
DragSpecOperationType,
DropTargetSpec,
EditableDragOperationType,
ElementModel,
GraphElement,
GraphElementProps,
isNode,
LabelBadge,
Layer,
Node,
observer,
Rect,
TOP_LAYER,
WithSelectionProps,
isNode,
observer,
useAnchor,
useCombineRefs,
useHover,
useDndDrop,
useDragNode,
useSelection,
withContextMenu,
withSelection,
WithSelectionProps,
} from '@patternfly/react-topology';
import clsx from 'clsx';
import { FunctionComponent, useContext, useRef } from 'react';
Expand All @@ -27,6 +39,7 @@ import { StepToolbar } from '../../Canvas/StepToolbar/StepToolbar';
import { NodeContextMenuFn } from '../ContextMenu/NodeContextMenu';
import { TargetAnchor } from '../target-anchor';
import './CustomNode.scss';
import { useEntityContext } from '../../../../hooks/useEntityContext/useEntityContext';

type DefaultNodeProps = Parameters<typeof DefaultNode>[0];
interface CustomNodeProps extends DefaultNodeProps, WithSelectionProps {
Expand All @@ -41,6 +54,7 @@ const CustomNode: FunctionComponent<CustomNodeProps> = observer(({ element, onCo
}

const vizNode: IVisualizationNode | undefined = element.getData()?.vizNode;
const entitiesContext = useEntityContext();
const settingsAdapter = useContext(SettingsContext);
const label = vizNode?.getNodeLabel(settingsAdapter.getSettings().nodeLabel);
const isDisabled = !!vizNode?.getComponentSchema()?.definition?.disabled;
Expand Down Expand Up @@ -70,10 +84,60 @@ const CustomNode: FunctionComponent<CustomNodeProps> = observer(({ element, onCo
return null;
}

const nodeDragSourceSpec: DragSourceSpec<
DragObjectWithType,
DragSpecOperationType<EditableDragOperationType>,
GraphElement,
object,
GraphElementProps
> = {
item: { type: '#node#' },
begin: () => {
const node = element as Node;

// Hide connected edges when dragging starts
node.getSourceEdges().forEach((edge) => edge.setVisible(false));
node.getTargetEdges().forEach((edge) => edge.setVisible(false));
},
canDrag: () => {
return element.getData()?.vizNode?.canDragNode() ? true : false;
},
end: () => {
const node = element as Node;

// Show edges again after dropping
node.getSourceEdges().forEach((edge) => edge.setVisible(true));
node.getTargetEdges().forEach((edge) => edge.setVisible(true));
},
};

const nodeDropTargetSpec: DropTargetSpec<GraphElement, unknown, object, GraphElementProps> = {
accept: ['#node#'],
canDrop: (item) => {
const targetNode = element as Node;
const draggedNode = item as Node;
// Ensure that the node is not dropped onto itself
return draggedNode !== targetNode;
},
drop: (item) => {
const draggedNodePath = item.getData().vizNode.data.path;

// Switch the positions of the dragged and target nodes
element.getData()?.vizNode?.switchSteps(draggedNodePath);

/** Update entity */
entitiesContext.updateEntitiesFromCamelResource();
},
};

const [_, dragNodeRef] = useDragNode(nodeDragSourceSpec);
const gCombinedRef = useCombineRefs<SVGGElement>(gHoverRef, dragNodeRef);
const [__, dropNodeRef] = useDndDrop(nodeDropTargetSpec);

return (
<Layer id={DEFAULT_LAYER}>
<g
ref={gHoverRef}
ref={gCombinedRef}
className="custom-node"
data-testid={`custom-node__${vizNode.id}`}
data-nodelabel={label}
Expand All @@ -83,7 +147,12 @@ const CustomNode: FunctionComponent<CustomNodeProps> = observer(({ element, onCo
onClick={onSelect}
onContextMenu={onContextMenu}
>
<foreignObject data-nodelabel={label} width={boxRef.current.width} height={boxRef.current.height}>
<foreignObject
data-nodelabel={label}
width={boxRef.current.width}
height={boxRef.current.height}
ref={dropNodeRef}
>
<div className="custom-node__container">
<div title={tooltipContent} className="custom-node__container__image">
<img src={vizNode.data.icon} />
Expand Down
10 changes: 10 additions & 0 deletions packages/ui/src/models/visualization/base-visual-entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ export interface BaseVisualCamelEntity extends BaseCamelEntity {
targetProperty?: string;
}) => void;

/** Check if the node is draggable */
isDraggableNode: (path?: string) => boolean;

/** Switch steps */
switchSteps: (options: { draggedNodePath: string; droppedNodePath?: string }) => void;

/** Remove the step at a given path from the underlying Camel entity */
removeStep: (path?: string) => void;

Expand Down Expand Up @@ -91,6 +97,10 @@ export interface IVisualizationNode<T extends IVisualizationNodeData = IVisualiz

addBaseEntityStep(definedComponent: DefinedComponent, mode: AddStepMode, targetProperty?: string): void;

canDragNode(): boolean | undefined;

switchSteps(path: string): void;

getNodeInteraction(): NodeInteraction;

setNodeInteraction(nodeInteraction: NodeInteraction): void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,35 @@ export abstract class AbstractCamelVisualEntity<T extends object> implements Bas
}
}

isDraggableNode(path?: string) {
if (path === 'route.from' || path === 'template.from') {
return false;
} else {
return true;
}
}

switchSteps(options: { draggedNodePath: string; droppedNodePath?: string }) {
if (options.droppedNodePath === undefined) return;

const pathArray = options.droppedNodePath.split('.');
const last = pathArray[pathArray.length - 1];
const penultimate = pathArray[pathArray.length - 2];

if (!Number.isInteger(Number(last)) && Number.isInteger(Number(penultimate))) {
const componentPath = options.draggedNodePath.split('.');
const componentModel = getValue(this.entityDef, componentPath?.slice(0, -1));

/** Remove the dragged node */
this.removeStep(options.draggedNodePath);

/** Add the dragged node before the drop target */
const desiredStartIndex = Number(penultimate);
const stepsArray: ProcessorDefinition[] = getValue(this.entityDef, pathArray.slice(0, -2), []);
stepsArray.splice(desiredStartIndex, 0, componentModel);
}
}

removeStep(path?: string): void {
if (!path) return;
const pathArray = path.split('.');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@ export class CamelErrorHandlerVisualEntity implements BaseVisualCamelEntity {
return;
}

isDraggableNode(_path?: string | undefined) {
return false;
}

switchSteps(_options: { draggedNodePath: string; droppedNodePath?: string | undefined }) {
return;
}

removeStep(): void {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ export class CamelRestConfigurationVisualEntity implements BaseVisualCamelEntity
return;
}

isDraggableNode(_path?: string | undefined) {
return false;
}

switchSteps(_options: { draggedNodePath: string; droppedNodePath?: string | undefined }) {
return;
}

removeStep(): void {
return;
}
Expand Down
26 changes: 26 additions & 0 deletions packages/ui/src/models/visualization/flows/pipe-visual-entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,32 @@ export class PipeVisualEntity implements BaseVisualCamelEntity {
}
}

isDraggableNode(path?: string | undefined) {
if (path === 'source' || path === 'sink') {
return false;
} else {
return true;
}
}

switchSteps(options: { draggedNodePath: string; droppedNodePath?: string | undefined }) {
if (
options.droppedNodePath === undefined ||
options.droppedNodePath === 'source' ||
options.droppedNodePath === 'sink'
)
return;

const step = getValue(this.pipe.spec!, options.draggedNodePath);
/** Remove the dragged node */
this.removeStep(options.draggedNodePath);

/** Add the dragged node before the drop target */
const kameletArray = getArrayProperty(this.pipe.spec!, 'steps') as PipeStep[];
const index = Number(options.droppedNodePath.split('.').pop());
kameletArray.splice(index, 0, step);
}

removeStep(path?: string): void {
/** This method needs to be enabled after passing the entire parent to this class*/
if (!path) return;
Expand Down
8 changes: 8 additions & 0 deletions packages/ui/src/models/visualization/visualization-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ class VisualizationNode<T extends IVisualizationNodeData = IVisualizationNodeDat
this.getBaseEntity()?.addStep({ definedComponent: definition, mode, data: this.data });
}

canDragNode(): boolean | undefined {
return this.getBaseEntity()?.isDraggableNode(this.data.path);
}

switchSteps(path: string): void {
this.getBaseEntity()?.switchSteps({ draggedNodePath: path, droppedNodePath: this.data.path });
}

getNodeInteraction(): NodeInteraction {
return (
this.nodeInteraction ?? this.getBaseEntity()?.getNodeInteraction(this.data) ?? this.DISABLED_NODE_INTERACTION
Expand Down

0 comments on commit b8e6180

Please sign in to comment.