Skip to content

Commit

Permalink
Edge labels (#111)
Browse files Browse the repository at this point in the history
* fix button color changes when option selected

* fix makeDecision implementation to not remove decsendants of the recently opened node

* update react flow minor version

* add new CustomBaseEdge which reflects react flow version 12's BaseEdge

* use my custom edge in DecisionEdge

* conditionally render edge label

* 0.8.0

* set children edges as undecided when nodes are removed

this is a temporary fix that we'll need to expand on as it does not fix edges recursively

* remove old isNumeric utility function
  • Loading branch information
dpgraham4401 authored Apr 30, 2024
1 parent c3259ff commit 15420e4
Show file tree
Hide file tree
Showing 9 changed files with 124 additions and 59 deletions.
84 changes: 42 additions & 42 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "the-manifest-game",
"private": true,
"version": "0.7.1",
"version": "0.8.0",
"type": "module",
"scripts": {
"dev": "vite",
Expand All @@ -17,7 +17,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^5.0.1",
"reactflow": "^11.10.4",
"reactflow": "^11.11.3",
"zustand": "^4.5.2"
},
"devDependencies": {
Expand Down
13 changes: 13 additions & 0 deletions src/components/Tree/Edges/CustomBaseEdge.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import '@testing-library/jest-dom';
import { cleanup, render, screen } from '@testing-library/react';
import { CustomBaseEdge } from 'components/Tree/Edges/CustomBaseEdge';
import { afterEach, describe, expect, test } from 'vitest';

afterEach(() => cleanup());

describe('Custom Base Edge', () => {
test('renders', () => {
render(<CustomBaseEdge path={''} />);
expect(screen.getByTestId('edgePath')).toBeInTheDocument();
});
});
38 changes: 38 additions & 0 deletions src/components/Tree/Edges/CustomBaseEdge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import cc from 'classcat';
import { BaseEdgeProps } from 'reactflow';

interface MyBaseEdgeProps extends BaseEdgeProps {
className?: string;
}

/**
* This component is copied from the React Flow library paired down/modified to include
* a className prop that allows us to apply a class to the edge. It reflects
* the changes to BaseEdge in the React Flow library version 12 (not released at the time of
* writing this code). When this project migrates to React Flow version 12, this file should
* be removed and our custom edges should be updated to use the v12 BaseEdge.
* https://github.com/xyflow/xyflow/blob/c318288703292d9faf03e3f6e31bfba82c540b2c/packages/react/src/components/Edges/BaseEdge.tsx#L7
*/
export function CustomBaseEdge({
id,
path,
style,
markerEnd,
markerStart,
className,
}: MyBaseEdgeProps) {
return (
<>
<path
id={id}
style={style}
d={path}
fill="none"
className={cc(['react-flow__edge-path', className])}
markerEnd={markerEnd}
markerStart={markerStart}
data-testid="edgePath"
/>
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ describe('Decision Edge', () => {
</ReactFlowProvider>
);
const edge = screen.getByTestId('1').querySelector('path');
expect(edge).toHaveStyle('stroke: #0D766E');
expect(edge).toHaveClass(/stroke/i);
});
test('no style when decision not made', () => {
render(
Expand Down
30 changes: 22 additions & 8 deletions src/components/Tree/Edges/DecisionEdge/DecisionEdge.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { BaseEdge, EdgeProps, getSmoothStepPath } from 'reactflow';
import { CustomBaseEdge } from 'components/Tree/Edges/CustomBaseEdge';
import { FaCheck } from 'react-icons/fa';
import { EdgeLabelRenderer, EdgeProps, getSmoothStepPath } from 'reactflow';

export interface DecisionEdgeData {
decisionMade?: boolean;
Expand All @@ -7,8 +9,8 @@ export interface DecisionEdgeData {
export interface DecisionEdgeProps extends EdgeProps<DecisionEdgeData> {}

export const DecisionEdge = (props: DecisionEdgeProps) => {
const { id, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition } = props;
const [edgePath] = getSmoothStepPath({
const { id, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, data } = props;
const [edgePath, labelX, labelY] = getSmoothStepPath({
sourceX,
sourceY,
targetX,
Expand All @@ -19,14 +21,26 @@ export const DecisionEdge = (props: DecisionEdgeProps) => {

return (
<>
<BaseEdge
<CustomBaseEdge
id={id}
path={edgePath}
style={{
stroke: props.data?.decisionMade ? '#0D766E' : '',
strokeWidth: '3px',
}}
className={`${data?.decisionMade ? 'stroke-green-600' : ''} stroke-2`}
/>
{data?.decisionMade && (
<EdgeLabelRenderer>
<div
className="rounded-full bg-green-600 p-2"
style={{
// see ReactFlow's documentation on labels for custom edges
// https://reactflow.dev/learn/customization/custom-edges#adding-an-edge-label
position: 'absolute',
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
}}
>
<FaCheck className="text-white" size={20} aria-label="selected decision" />
</div>
</EdgeLabelRenderer>
)}
</>
);
};
9 changes: 4 additions & 5 deletions src/components/Tree/Nodes/BoolNode/BoolButton/BoolButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ interface BoolButtonProps {
* @constructor
*/
export const BoolButton = ({ onClick, id, response, selected }: BoolButtonProps) => {
console.log(selected && 'foo');
return (
<div>
<button
Expand All @@ -23,10 +22,10 @@ export const BoolButton = ({ onClick, id, response, selected }: BoolButtonProps)
}
data-testid={`${id}-${response ? 'yes' : 'no'}-button`}
className={
'mb-1 rounded-xl bg-slate-600 px-2 py-1 text-2xl font-semibold text-white ' +
'transition-colors duration-200 ease-in-out hover:bg-slate-700 ' +
'focus:outline-none focus:ring-2 focus:ring-slate-50 active:bg-slate-800' +
(selected ? ' bg-green-600 hover:bg-green-700' : '')
' mb-1 rounded-xl px-2 py-1 text-2xl font-semibold text-white ' +
' transition-colors duration-200 ease-in-out ' +
' focus:outline-none focus:ring-2 focus:ring-slate-50 active:bg-slate-800 ' +
(selected ? ' bg-green-600 hover:bg-green-700 ' : ' bg-slate-600 hover:bg-slate-700 ')
}
>
<div className="flex min-w-20 items-center">
Expand Down
1 change: 0 additions & 1 deletion src/hooks/useDecisionTree/useDecisionTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ export const useDecisionTree = (initialTree?: PositionUnawareDecisionTree) => {
hideNiblings(source);
setDecisionMade(source);
addDecisionToPath(source, target);
hideDescendants(target);
};

const retractDecision = (target: string) => {
Expand Down
2 changes: 2 additions & 0 deletions src/store/TreeSlice/treeSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,12 @@ export const createTreeSlice: StateCreator<
const childrenIds = getDescendantIds(get().tree, nodeId);
get().collapseDecision(nodeId, childrenIds);
get().removeDagNodes([...childrenIds]);
get().setChildrenEdgesUndecided(nodeId);
},
hideNiblings: (nodeId: string) => {
const dagTree = get().tree;
const siblingIds = getSiblingIds(dagTree, nodeId);
siblingIds.map((id) => get().setChildrenEdgesUndecided(id));
const siblingDescendantIds = siblingIds.flatMap((id) => getDescendantIds(dagTree, id));
get().collapseDecision(nodeId, siblingDescendantIds);
get().removeDagNodes([...siblingDescendantIds]);
Expand Down

0 comments on commit 15420e4

Please sign in to comment.