Skip to content

Commit

Permalink
Add edge deletion and custom edge styling (#52)
Browse files Browse the repository at this point in the history
Signed-off-by: Tyler Ohlsen <[email protected]>
(cherry picked from commit d2b33f6)
  • Loading branch information
ohltyler authored and github-actions[bot] committed Oct 5, 2023
1 parent 319aa10 commit 12348c7
Show file tree
Hide file tree
Showing 9 changed files with 124 additions and 15 deletions.
20 changes: 19 additions & 1 deletion public/pages/workflow_detail/workspace/reactflow-styles.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
$handle-color: #5a5a5a;
$handle-color-valid: #55dd99;
$handle-color-invalid: #ff6060;

.reactflow-parent-wrapper {
display: flex;
flex-grow: 1;
Expand All @@ -15,6 +19,20 @@
padding: 0;
}

.workspace-component {
.reactflow-workspace .react-flow__node {
width: 300px;
}

.reactflow-workspace .react-flow__handle {
height: 10px;
width: 10px;
background: $handle-color;
}

.reactflow-workspace .react-flow__handle-connecting {
background: $handle-color-invalid;
}

.reactflow-workspace .react-flow__handle-valid {
background: $handle-color-valid;
}
15 changes: 10 additions & 5 deletions public/pages/workflow_detail/workspace/workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,19 @@ import { IComponent, Workflow } from '../../../../common';
import { generateId } from '../../../utils';
import { getCore } from '../../../services';
import { WorkspaceComponent } from '../workspace_component';
import { DeletableEdge } from '../workspace_edge';

// styling
import 'reactflow/dist/style.css';
import './reactflow-styles.scss';
import '../workspace_edge/deletable-edge-styles.scss';

interface WorkspaceProps {
workflow?: Workflow;
}

const nodeTypes = { customComponent: WorkspaceComponent };
// TODO: probably have custom edge types here too
const edgeTypes = { customEdge: DeletableEdge };

export function Workspace(props: WorkspaceProps) {
const reactFlowWrapper = useRef(null);
Expand All @@ -38,11 +40,12 @@ export function Workspace(props: WorkspaceProps) {

const onConnect = useCallback(
(params) => {
setEdges((eds) => addEdge(params, eds));
const edge = {
...params,
type: 'customEdge',
};
setEdges((eds) => addEdge(edge, eds));
},
// TODO: add customized logic to prevent connections based on the node's
// allowed inputs. If allowed, update that node state as well with the added
// connection details.
[setEdges]
);

Expand Down Expand Up @@ -132,12 +135,14 @@ export function Workspace(props: WorkspaceProps) {
nodes={nodes}
edges={edges}
nodeTypes={nodeTypes}
edgeTypes={edgeTypes}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
onInit={setReactFlowInstance}
onDrop={onDrop}
onDragOver={onDragOver}
className="reactflow-workspace"
fitView
>
<Controls />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@ export function InputHandle(props: InputHandleProps) {
isValidConnection(connection, reactFlowInstance)
}
style={{
height: 10,
width: 10,
backgroundColor: 'black',
top: position,
}}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,6 @@ export function OutputHandle(props: OutputHandleProps) {
isValidConnection(connection, reactFlowInstance)
}
style={{
height: 10,
width: 10,
backgroundColor: 'black',
top: position,
}}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export function WorkspaceComponent(props: WorkspaceComponentProps) {
: component.fields;

return (
<EuiCard title={component.label} className="workspace-component">
<EuiCard title={component.label}>
<EuiFlexGroup direction="column">
{/* <EuiFlexItem>
{component.allowsCreation ? (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.delete-edge-button {
width: 20px;
height: 20px;
background: #eee;
border: 1px solid #fff;
cursor: pointer;
border-radius: 50%;
font-size: 12px;
line-height: 1;
}

.delete-edge-button:hover {
box-shadow: 0 0 6px 2px rgba(0, 0, 0, 0.08);
}
66 changes: 66 additions & 0 deletions public/pages/workflow_detail/workspace_edge/deletable_edge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useContext } from 'react';
import {
BaseEdge,
EdgeLabelRenderer,
EdgeProps,
getBezierPath,
} from 'reactflow';
import { rfContext } from '../../../store';

// styling
import './deletable-edge-styles.scss';

type DeletableEdgeProps = EdgeProps;

/**
* A custom deletable edge. Renders a delete button in the center of the edge once connected.
* Using bezier path by default. For all edge types,
* see https://reactflow.dev/docs/examples/edges/edge-types/
*/
export function DeletableEdge(props: DeletableEdgeProps) {
const [edgePath, labelX, labelY] = getBezierPath({
sourceX: props.sourceX,
sourceY: props.sourceY,
sourcePosition: props.sourcePosition,
targetX: props.targetX,
targetY: props.targetY,
targetPosition: props.targetPosition,
});

const { deleteEdge } = useContext(rfContext);

const onEdgeClick = (event: any, edgeId: string) => {
event.stopPropagation();
deleteEdge(edgeId);
};

return (
<>
<BaseEdge path={edgePath} markerEnd={props.markerEnd} />
<EdgeLabelRenderer>
{/** Using in-line styling since scss can't support dynamic values*/}
<div
style={{
position: 'absolute',
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
fontSize: 12,
pointerEvents: 'all',
}}
className="nodrag nopan"
>
<button
className="delete-edge-button"
onClick={(event) => onEdgeClick(event, props.id)}
>
×
</button>
</div>
</EdgeLabelRenderer>
</>
);
}
6 changes: 6 additions & 0 deletions public/pages/workflow_detail/workspace_edge/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

export { DeletableEdge } from './deletable_edge';
10 changes: 8 additions & 2 deletions public/store/context/react_flow_context_provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
*/

import React, { createContext, useState } from 'react';
import { useDispatch } from 'react-redux';
import { Edge } from 'reactflow';
import { setDirty } from '../reducers';

const initialValues = {
reactFlowInstance: null,
Expand All @@ -23,6 +26,7 @@ export const rfContext = createContext(initialValues);
* nested child components.
*/
export function ReactFlowContextProvider({ children }: any) {
const dispatch = useDispatch();
const [reactFlowInstance, setReactFlowInstance] = useState(null);

const deleteNode = (nodeId: string) => {
Expand All @@ -31,8 +35,10 @@ export function ReactFlowContextProvider({ children }: any) {
};

const deleteEdge = (edgeId: string) => {
// TODO: implement edge deletion
// reactFlowInstance.setEdges(...)
reactFlowInstance.setEdges(
reactFlowInstance.getEdges().filter((edge: Edge) => edge.id !== edgeId)
);
dispatch(setDirty());
};

return (
Expand Down

0 comments on commit 12348c7

Please sign in to comment.