From 15420e42f44713513651f9540e38c0673766864f Mon Sep 17 00:00:00 2001 From: David Paul Graham <43794491+dpgraham4401@users.noreply.github.com> Date: Tue, 30 Apr 2024 18:24:15 -0400 Subject: [PATCH] Edge labels (#111) * 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 --- package-lock.json | 84 +++++++++---------- package.json | 4 +- .../Tree/Edges/CustomBaseEdge.spec.tsx | 13 +++ src/components/Tree/Edges/CustomBaseEdge.tsx | 38 +++++++++ .../Edges/DecisionEdge/DecisionEdge.spec.tsx | 2 +- .../Tree/Edges/DecisionEdge/DecisionEdge.tsx | 30 +++++-- .../Nodes/BoolNode/BoolButton/BoolButton.tsx | 9 +- src/hooks/useDecisionTree/useDecisionTree.tsx | 1 - src/store/TreeSlice/treeSlice.ts | 2 + 9 files changed, 124 insertions(+), 59 deletions(-) create mode 100644 src/components/Tree/Edges/CustomBaseEdge.spec.tsx create mode 100644 src/components/Tree/Edges/CustomBaseEdge.tsx diff --git a/package-lock.json b/package-lock.json index f7a50f9..0aced19 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,18 @@ { "name": "the-manifest-game", - "version": "0.7.1", + "version": "0.8.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "the-manifest-game", - "version": "0.7.1", + "version": "0.8.0", "dependencies": { "@dagrejs/dagre": "^1.0.4", "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": { @@ -1465,11 +1465,11 @@ } }, "node_modules/@reactflow/background": { - "version": "11.3.9", - "resolved": "https://registry.npmjs.org/@reactflow/background/-/background-11.3.9.tgz", - "integrity": "sha512-byj/G9pEC8tN0wT/ptcl/LkEP/BBfa33/SvBkqE4XwyofckqF87lKp573qGlisfnsijwAbpDlf81PuFL41So4Q==", + "version": "11.3.13", + "resolved": "https://registry.npmjs.org/@reactflow/background/-/background-11.3.13.tgz", + "integrity": "sha512-hkvpVEhgvfTDyCvdlitw4ioKCYLaaiRXnuEG+1QM3Np+7N1DiWF1XOv5I8AFyNoJL07yXEkbECUTsHvkBvcG5A==", "dependencies": { - "@reactflow/core": "11.10.4", + "@reactflow/core": "11.11.3", "classcat": "^5.0.3", "zustand": "^4.4.1" }, @@ -1479,11 +1479,11 @@ } }, "node_modules/@reactflow/controls": { - "version": "11.2.9", - "resolved": "https://registry.npmjs.org/@reactflow/controls/-/controls-11.2.9.tgz", - "integrity": "sha512-e8nWplbYfOn83KN1BrxTXS17+enLyFnjZPbyDgHSRLtI5ZGPKF/8iRXV+VXb2LFVzlu4Wh3la/pkxtfP/0aguA==", + "version": "11.2.13", + "resolved": "https://registry.npmjs.org/@reactflow/controls/-/controls-11.2.13.tgz", + "integrity": "sha512-3xgEg6ALIVkAQCS4NiBjb7ad8Cb3D8CtA7Vvl4Hf5Ar2PIVs6FOaeft9s2iDZGtsWP35ECDYId1rIFVhQL8r+A==", "dependencies": { - "@reactflow/core": "11.10.4", + "@reactflow/core": "11.11.3", "classcat": "^5.0.3", "zustand": "^4.4.1" }, @@ -1493,9 +1493,9 @@ } }, "node_modules/@reactflow/core": { - "version": "11.10.4", - "resolved": "https://registry.npmjs.org/@reactflow/core/-/core-11.10.4.tgz", - "integrity": "sha512-j3i9b2fsTX/sBbOm+RmNzYEFWbNx4jGWGuGooh2r1jQaE2eV+TLJgiG/VNOp0q5mBl9f6g1IXs3Gm86S9JfcGw==", + "version": "11.11.3", + "resolved": "https://registry.npmjs.org/@reactflow/core/-/core-11.11.3.tgz", + "integrity": "sha512-+adHdUa7fJSEM93fWfjQwyWXeI92a1eLKwWbIstoCakHpL8UjzwhEh6sn+mN2h/59MlVI7Ehr1iGTt3MsfcIFA==", "dependencies": { "@types/d3": "^7.4.0", "@types/d3-drag": "^3.0.1", @@ -1513,11 +1513,11 @@ } }, "node_modules/@reactflow/minimap": { - "version": "11.7.9", - "resolved": "https://registry.npmjs.org/@reactflow/minimap/-/minimap-11.7.9.tgz", - "integrity": "sha512-le95jyTtt3TEtJ1qa7tZ5hyM4S7gaEQkW43cixcMOZLu33VAdc2aCpJg/fXcRrrf7moN2Mbl9WIMNXUKsp5ILA==", + "version": "11.7.13", + "resolved": "https://registry.npmjs.org/@reactflow/minimap/-/minimap-11.7.13.tgz", + "integrity": "sha512-m2MvdiGSyOu44LEcERDEl1Aj6x//UQRWo3HEAejNU4HQTlJnYrSN8tgrYF8TxC1+c/9UdyzQY5VYgrTwW4QWdg==", "dependencies": { - "@reactflow/core": "11.10.4", + "@reactflow/core": "11.11.3", "@types/d3-selection": "^3.0.3", "@types/d3-zoom": "^3.0.1", "classcat": "^5.0.3", @@ -1531,11 +1531,11 @@ } }, "node_modules/@reactflow/node-resizer": { - "version": "2.2.9", - "resolved": "https://registry.npmjs.org/@reactflow/node-resizer/-/node-resizer-2.2.9.tgz", - "integrity": "sha512-HfickMm0hPDIHt9qH997nLdgLt0kayQyslKE0RS/GZvZ4UMQJlx/NRRyj5y47Qyg0NnC66KYOQWDM9LLzRTnUg==", + "version": "2.2.13", + "resolved": "https://registry.npmjs.org/@reactflow/node-resizer/-/node-resizer-2.2.13.tgz", + "integrity": "sha512-X7ceQ2s3jFLgbkg03n2RYr4hm3jTVrzkW2W/8ANv/SZfuVmF8XJxlERuD8Eka5voKqLda0ywIZGAbw9GoHLfUQ==", "dependencies": { - "@reactflow/core": "11.10.4", + "@reactflow/core": "11.11.3", "classcat": "^5.0.4", "d3-drag": "^3.0.0", "d3-selection": "^3.0.0", @@ -1547,11 +1547,11 @@ } }, "node_modules/@reactflow/node-toolbar": { - "version": "1.3.9", - "resolved": "https://registry.npmjs.org/@reactflow/node-toolbar/-/node-toolbar-1.3.9.tgz", - "integrity": "sha512-VmgxKmToax4sX1biZ9LXA7cj/TBJ+E5cklLGwquCCVVxh+lxpZGTBF3a5FJGVHiUNBBtFsC8ldcSZIK4cAlQww==", + "version": "1.3.13", + "resolved": "https://registry.npmjs.org/@reactflow/node-toolbar/-/node-toolbar-1.3.13.tgz", + "integrity": "sha512-aknvNICO10uWdthFSpgD6ctY/CTBeJUMV9co8T9Ilugr08Nb89IQ4uD0dPmr031ewMQxixtYIkw+sSDDzd2aaQ==", "dependencies": { - "@reactflow/core": "11.10.4", + "@reactflow/core": "11.11.3", "classcat": "^5.0.3", "zustand": "^4.4.1" }, @@ -2047,9 +2047,9 @@ } }, "node_modules/@types/d3-hierarchy": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.6.tgz", - "integrity": "sha512-qlmD/8aMk5xGorUvTUWHCiumvgaUXYldYjNVOWtYoTYY/L+WwIEAmJxUmTgr9LoGNG0PPAOmqMDJVDPc7DOpPw==" + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==" }, "node_modules/@types/d3-interpolate": { "version": "3.0.4", @@ -3208,9 +3208,9 @@ } }, "node_modules/classcat": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.4.tgz", - "integrity": "sha512-sbpkOw6z413p+HDGcBENe498WM9woqWHiJxCq7nvmxe9WmrUmqfAcxpIwAiMtM5Q3AhYkzXcNQHqsWq0mND51g==" + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", + "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==" }, "node_modules/cli-spinners": { "version": "2.9.2", @@ -6840,16 +6840,16 @@ } }, "node_modules/reactflow": { - "version": "11.10.4", - "resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.10.4.tgz", - "integrity": "sha512-0CApYhtYicXEDg/x2kvUHiUk26Qur8lAtTtiSlptNKuyEuGti6P1y5cS32YGaUoDMoCqkm/m+jcKkfMOvSCVRA==", - "dependencies": { - "@reactflow/background": "11.3.9", - "@reactflow/controls": "11.2.9", - "@reactflow/core": "11.10.4", - "@reactflow/minimap": "11.7.9", - "@reactflow/node-resizer": "2.2.9", - "@reactflow/node-toolbar": "1.3.9" + "version": "11.11.3", + "resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.11.3.tgz", + "integrity": "sha512-wusd1Xpn1wgsSEv7UIa4NNraCwH9syBtubBy4xVNXg3b+CDKM+sFaF3hnMx0tr0et4km9urIDdNvwm34QiZong==", + "dependencies": { + "@reactflow/background": "11.3.13", + "@reactflow/controls": "11.2.13", + "@reactflow/core": "11.11.3", + "@reactflow/minimap": "11.7.13", + "@reactflow/node-resizer": "2.2.13", + "@reactflow/node-toolbar": "1.3.13" }, "peerDependencies": { "react": ">=17", diff --git a/package.json b/package.json index 661e5c1..644384b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "the-manifest-game", "private": true, - "version": "0.7.1", + "version": "0.8.0", "type": "module", "scripts": { "dev": "vite", @@ -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": { diff --git a/src/components/Tree/Edges/CustomBaseEdge.spec.tsx b/src/components/Tree/Edges/CustomBaseEdge.spec.tsx new file mode 100644 index 0000000..a42bff9 --- /dev/null +++ b/src/components/Tree/Edges/CustomBaseEdge.spec.tsx @@ -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(); + expect(screen.getByTestId('edgePath')).toBeInTheDocument(); + }); +}); diff --git a/src/components/Tree/Edges/CustomBaseEdge.tsx b/src/components/Tree/Edges/CustomBaseEdge.tsx new file mode 100644 index 0000000..ece7254 --- /dev/null +++ b/src/components/Tree/Edges/CustomBaseEdge.tsx @@ -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 ( + <> + + > + ); +} diff --git a/src/components/Tree/Edges/DecisionEdge/DecisionEdge.spec.tsx b/src/components/Tree/Edges/DecisionEdge/DecisionEdge.spec.tsx index 8baf35a..3668b52 100644 --- a/src/components/Tree/Edges/DecisionEdge/DecisionEdge.spec.tsx +++ b/src/components/Tree/Edges/DecisionEdge/DecisionEdge.spec.tsx @@ -56,7 +56,7 @@ describe('Decision Edge', () => { ); 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( diff --git a/src/components/Tree/Edges/DecisionEdge/DecisionEdge.tsx b/src/components/Tree/Edges/DecisionEdge/DecisionEdge.tsx index c1fbbd0..2e54fc9 100644 --- a/src/components/Tree/Edges/DecisionEdge/DecisionEdge.tsx +++ b/src/components/Tree/Edges/DecisionEdge/DecisionEdge.tsx @@ -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; @@ -7,8 +9,8 @@ export interface DecisionEdgeData { export interface DecisionEdgeProps extends EdgeProps {} 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, @@ -19,14 +21,26 @@ export const DecisionEdge = (props: DecisionEdgeProps) => { return ( <> - + {data?.decisionMade && ( + + + + + + )} > ); }; diff --git a/src/components/Tree/Nodes/BoolNode/BoolButton/BoolButton.tsx b/src/components/Tree/Nodes/BoolNode/BoolButton/BoolButton.tsx index d863b75..c66887c 100644 --- a/src/components/Tree/Nodes/BoolNode/BoolButton/BoolButton.tsx +++ b/src/components/Tree/Nodes/BoolNode/BoolButton/BoolButton.tsx @@ -13,7 +13,6 @@ interface BoolButtonProps { * @constructor */ export const BoolButton = ({ onClick, id, response, selected }: BoolButtonProps) => { - console.log(selected && 'foo'); return ( diff --git a/src/hooks/useDecisionTree/useDecisionTree.tsx b/src/hooks/useDecisionTree/useDecisionTree.tsx index 4cdb8f2..4ae89e9 100644 --- a/src/hooks/useDecisionTree/useDecisionTree.tsx +++ b/src/hooks/useDecisionTree/useDecisionTree.tsx @@ -50,7 +50,6 @@ export const useDecisionTree = (initialTree?: PositionUnawareDecisionTree) => { hideNiblings(source); setDecisionMade(source); addDecisionToPath(source, target); - hideDescendants(target); }; const retractDecision = (target: string) => { diff --git a/src/store/TreeSlice/treeSlice.ts b/src/store/TreeSlice/treeSlice.ts index 148f517..10fa105 100644 --- a/src/store/TreeSlice/treeSlice.ts +++ b/src/store/TreeSlice/treeSlice.ts @@ -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]);