Skip to content

Commit

Permalink
Persist node selection state and set default select styles
Browse files Browse the repository at this point in the history
Signed-off-by: Tyler Ohlsen <[email protected]>
  • Loading branch information
ohltyler committed Oct 19, 2023
1 parent 896fa97 commit 5c05976
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 48 deletions.
1 change: 1 addition & 0 deletions public/component_types/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,5 @@ export interface IComponent {
*/
export interface IComponentData extends IComponent {
id: string;
selected?: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,17 @@
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useState, useContext } from 'react';
import { useOnSelectionChange } from 'reactflow';
import React from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
import { ReactFlowComponent } from '../../../../common';
import { rfContext } from '../../../store';
import { ComponentInputs } from './component_inputs';
import { EmptyComponentInputs } from './empty_component_inputs';

// styling
import '../workspace/workspace-styles.scss';

interface ComponentDetailsProps {
onToggleChange: () => void;
isOpen: boolean;
selectedComponent?: ReactFlowComponent;
}

/**
Expand All @@ -25,33 +22,6 @@ interface ComponentDetailsProps {
* in the flow workspace.
*/
export function ComponentDetails(props: ComponentDetailsProps) {
// TODO: use this instance to update the internal node state. ex: update field data in the selected node based
// on user input
const { reactFlowInstance } = useContext(rfContext);

const [selectedComponent, setSelectedComponent] = useState<
ReactFlowComponent
>();

/**
* Hook provided by reactflow to listen on when nodes are selected / de-selected.
* - populate panel content appropriately
* - open the panel if a node is selected and the panel is closed
* - it is assumed that only one node can be selected at once
*/
useOnSelectionChange({
onChange: ({ nodes, edges }) => {
if (nodes && nodes.length > 0) {
setSelectedComponent(nodes[0]);
if (!props.isOpen) {
props.onToggleChange();
}
} else {
setSelectedComponent(undefined);
}
},
});

return (
<EuiFlexGroup
direction="column"
Expand All @@ -60,8 +30,8 @@ export function ComponentDetails(props: ComponentDetailsProps) {
>
<EuiFlexItem className="resizable-panel-border">
<EuiPanel paddingSize="m">
{selectedComponent ? (
<ComponentInputs selectedComponent={selectedComponent} />
{props.selectedComponent ? (
<ComponentInputs selectedComponent={props.selectedComponent} />
) : (
<EmptyComponentInputs />
)}
Expand Down
15 changes: 9 additions & 6 deletions public/pages/workflow_detail/workflow_detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import React, { useEffect } from 'react';
import { RouteComponentProps } from 'react-router-dom';
import { useSelector } from 'react-redux';
import { ReactFlowProvider } from 'reactflow';
import { EuiPage, EuiPageBody } from '@elastic/eui';
import { BREADCRUMBS } from '../../utils';
import { getCore } from '../../services';
Expand Down Expand Up @@ -42,11 +43,13 @@ export function WorkflowDetail(props: WorkflowDetailProps) {
});

return (
<EuiPage>
<EuiPageBody>
<WorkflowDetailHeader workflow={workflow} />
<ResizableWorkspace workflow={workflow} />
</EuiPageBody>
</EuiPage>
<ReactFlowProvider>
<EuiPage>
<EuiPageBody>
<WorkflowDetailHeader workflow={workflow} />
<ResizableWorkspace workflow={workflow} />
</EuiPageBody>
</EuiPage>
</ReactFlowProvider>
);
}
10 changes: 10 additions & 0 deletions public/pages/workflow_detail/workspace/reactflow-styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ $handle-color-invalid: $euiColorDanger;
width: 300px;
}

// Overriding the styling for the reactflow node when it is selected.
// We need to use important tag to override ReactFlow's wrapNode that sets the box-shadow.
// Ref: https://github.com/wbkd/react-flow/blob/main/packages/core/src/components/Nodes/wrapNode.tsx#L187
.reactflow-workspace .react-flow__node-customComponent.selected {
box-shadow: 0 0 2px 2px rgba('0, 0, 0', 0.5);
&:focus {
box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.5) !important;
}
}

.reactflow-workspace .react-flow__handle {
height: 10px;
width: 10px;
Expand Down
51 changes: 43 additions & 8 deletions public/pages/workflow_detail/workspace/resizable_workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useRef, useState, useEffect } from 'react';
import { ReactFlowProvider } from 'reactflow';
import React, { useRef, useState, useEffect, useContext } from 'react';
import { useOnSelectionChange } from 'reactflow';
import { Form, Formik } from 'formik';
import * as yup from 'yup';
import { EuiButton, EuiResizableContainer } from '@elastic/eui';
Expand All @@ -17,6 +17,7 @@ import {
componentDataToFormik,
getComponentSchema,
} from '../../../../common';
import { rfContext } from '../../../store';
import { Workspace } from './workspace';
import { ComponentDetails } from '../component_details';

Expand All @@ -41,6 +42,43 @@ export function ResizableWorkspace(props: ResizableWorkspaceProps) {
setIsOpen(!isOpen);
};

// Selected component state
const { reactFlowInstance } = useContext(rfContext);
const [selectedComponent, setSelectedComponent] = useState<
ReactFlowComponent
>();

/**
* Hook provided by reactflow to listen on when nodes are selected / de-selected.
* - populate panel content appropriately
* - open the panel if a node is selected and the panel is closed
* - it is assumed that only one node can be selected at once
*/
useOnSelectionChange({
onChange: ({ nodes, edges }) => {
if (nodes && nodes.length > 0) {
setSelectedComponent(nodes[0]);
if (!isOpen) {
onToggleChange();
}
} else {
setSelectedComponent(undefined);
}
},
});

useEffect(() => {
reactFlowInstance?.setNodes((nodes: ReactFlowComponent[]) =>
nodes.map((node) => {
node.data = {
...node.data,
selected: node.id === selectedComponent?.id ? true : false,
};
return node;
})
);
}, [selectedComponent]);

// Formik form state
const [formValues, setFormValues] = useState<WorkspaceFormValues>({});
const [formSchema, setFormSchema] = useState<WorkspaceSchema>(yup.object({}));
Expand Down Expand Up @@ -110,7 +148,7 @@ export function ResizableWorkspace(props: ResizableWorkspaceProps) {
}

return (
<ReactFlowProvider>
<>
<EuiResizablePanel mode="main" initialSize={75} minSize="50%">
<Workspace
workflow={props.workflow}
Expand All @@ -125,12 +163,9 @@ export function ResizableWorkspace(props: ResizableWorkspaceProps) {
minSize="10%"
onToggleCollapsedInternal={() => onToggleChange()}
>
<ComponentDetails
onToggleChange={onToggleChange}
isOpen={isOpen}
/>
<ComponentDetails selectedComponent={selectedComponent} />
</EuiResizablePanel>
</ReactFlowProvider>
</>
);
}}
</EuiResizableContainer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ export function DeletableEdge(props: DeletableEdgeProps) {
const { deleteEdge } = useContext(rfContext);

const onEdgeClick = (event: any, edgeId: string) => {
// Prevent this event from bubbling up and putting reactflow into an unexpected state.
// This implementation follows the doc example: https://reactflow.dev/docs/examples/edges/custom-edge/
event.stopPropagation();
deleteEdge(edgeId);
};
Expand Down

0 comments on commit 5c05976

Please sign in to comment.