diff --git a/README.md b/README.md
index 728d6d2..7c371af 100644
--- a/README.md
+++ b/README.md
@@ -2,11 +2,14 @@
## About
+Mathnetic_Carter is a branch of Mathnetic. This branch is designed for students who have the motor skills for using a keyboard and touchpad rather than touchscreen. The main Mathnetic branch will have touchscreen support and will be used as a more generalist program used in classes such as Life Skills.
+
Mathnetic is an assistive learning tool designed to help a student with cerebral palsy in their math-related endeavours. Mathnetic is designed to be a block-based editor, allowing mathematical symbols and text to be dragged to a primary workspace and be *snapped* together, in a very similar fashion to the popular block-based coding editor, [Scratch](https://scratch.mit.edu/).
Made with Love by Team Sming!!!!!!!!!
Founded by Lord Sming's Class of 2025.
+Mathnetic's legacy carried on by Lord Sming's Class of 2026.
## Requirements
@@ -43,5 +46,4 @@ Here's a list of software, libraries, and frameworks you should be familiar with
* React
* HTML5 & JSX
* CSS
-* npm
-* Figma
+* npm
\ No newline at end of file
diff --git a/src/App.jsx b/src/App.jsx
index 71b78ea..8176978 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -18,7 +18,7 @@ import {
getIncomers,
getOutgoers,
getConnectedEdges,
- useNodeConnections,
+ useNodeConnections
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import { DndProvider } from 'react-dnd';
@@ -29,19 +29,20 @@ import { useDrop } from 'react-dnd';
import Sidebar from './components/SideBar';
import OutputPane from './components/OutputPane';
import ContextMenu from './components/ContextMenu';
+import TrashButton from './components/DeleteButton';
import { TypeProvider, useType } from './components/context/TypeContext';
import { LatexEqProvider, useLatexEq } from './components/context/LatexEqContext';
-import NumericNode from './components/node_types/NumericNode'; // All node types
-import LaTeXNode from './components/node_types/LaTeXNode';
-import ArithmeticNode from './components/node_types/ArithmeticNode';
-import VariableNode from './components/node_types/VariableNode';
-import NewNode from './components/node_types/NewNode';
-import VerticalConnector from './components/node_types/VerticalConnector';
-import ExponentNode from './components/node_types/ExponentNode';
-import FractionNode from './components/node_types/FractionNode';
-import StartNode from './components/node_types/StartNode';
+import NumericNode from './components/node_types/NumericNode'; // nodes for integer numbers
+import LaTeXNode from './components/node_types/LaTeXNode'; //node for complex math symbols, ex fractions (https://latex.js.org/usage.html#library)
+import ArithmeticNode from './components/node_types/ArithmeticNode'; //nodes for math operators (+ - * /)
+import VariableNode from './components/node_types/VariableNode'; //nodes for letter variables
+import NewNode from './components/node_types/NewNode'; //a node we (co2026) used for testing nodes that create other nodes (ex. FractionNode). has no real purpose now
+import VerticalConnector from './components/node_types/VerticalConnector'; //connector node used and created by fractionnode
+import ExponentNode from './components/node_types/ExponentNode'; //exponentnode can be clicked and snapped onto any other node
+import FractionNode from './components/node_types/FractionNode'; //fractionnode creates two verticalconnector nodes. one above and one below it.
+import StartNode from './components/node_types/StartNode'; //signals to the computer that it should send output data starting with the node that it connects to
const nodeTypes = {
numeric: NumericNode,
@@ -58,11 +59,15 @@ const nodeTypes = {
const upperConnectionTypes = ['test', 'fraction']; // Keep track of which types need upper and lower connections
const lowerConnectionTypes = ['test', 'fraction'];
-const source1Types = ['numeric', 'latex', 'arithmetic', 'variable', 'test','connector', 'fraction', 'start'];
-const target1Types = ['numeric', 'latex', 'arithmetic', 'variable', 'test', 'fraction'];
-const source2Types = ['numeric', 'latex', 'arithmetic', 'variable'];
-const target2Types = ['numeric', 'latex', 'arithmetic', 'variable'];
+const source1Types = ['numeric', 'latex', 'arithmetic', 'variable', 'test','connector', 'fraction', 'start']; //a list of node types that can connect to other nodes [X]->-[ ]
+const target1Types = ['numeric', 'latex', 'arithmetic', 'variable', 'test', 'fraction']; //a list of nodes that can be connected to [ ]->-[X]
+const source2Types = []; //a list of nodes that can connect to other nodes vertically [X]
+ // |
+ // [ ]
+const target2Types = [] ;//a list of nodes that can be connected to other nodes vertically [ ]
+ // |
+ // [X]
// Variables to Track How Nodes Are Arranged
let groupNum = 0;
let rowNum = 0;
@@ -72,33 +77,38 @@ let colNum = 0;
let id = 0;
const getId = () => `dndnode_${id++}`;
+// Assign a variable for when a clicked node should be deleted
+let isDeleting = false;
+
// Proximity Connection Variables
const MIN_DISTANCE = 200;
-// connector node distances
-const LOWER_POSITION_REL = { x: -20, y: 50 };
-const UPPER_POSITION_REL = { x: -20, y: -30 };
-const EXP_POSITION_REL = {x: 50, y: -25}
+// Constants to represent the position offsets of nodes that are tied to a parent node
+const LOWER_POSITION_REL = { x: 3, y: 80 };
+const UPPER_POSITION_REL = { x: 3, y: -80 };
+const EXP_POSITION_REL = { x: 50, y: -25 }
+
const Flow = () => {
- const [nodes, setNodes, onNodesChange] = useNodesState([]);
+ const [nodes, setNodes, onNodesChange] = useNodesState([]); // Declaring states and state changers for nodes and edges.
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
- const [menu, setMenu] = useState(null);
+ const [menu, setMenu] = useState(null); // Context menu for right click (used for testing)
const ref = useRef(null);
- let lastClicked = useRef(null);
- const reactFlowInstance = useReactFlow();
+ let lastClicked = useRef(null); // Used to track the last node that has been clicked (for exponent node)
+ const reactFlowInstance = useReactFlow(); // useReactFlow() gets a react flow instance or some of its functions
const {getNode, deleteElements} = useReactFlow();
- const moveNode = (nodeId, newX, newY) => {
- setNodes((nds) =>
- nds.map((node) => {
- if (node.id === nodeId) {
- return { ...node, position: { x: newX, y: newY } };
+ const moveNode = (nodeId, newX, newY) => { // Helper function to move a node. This form of state setting is common throughout the program
+ setNodes((nds) => // Nodes should never be modified directly, you should instead use setNodes.
+ nds.map((node) => { // the map function goes through the array and fills in a new array by running the function for each node
+ if (node.id === nodeId) { // In this function, the node is returned (added back the same) if it is not the node being moved, and the node that is being moved
+ return { ...node, position: { x: newX, y: newY } }; // is replaced by an identical node with a different position
}
return node;
})
);
};
+
const onNodeContextMenu = useCallback(
(event, node) => {
// Prevent native context menu from showing
@@ -120,11 +130,21 @@ const Flow = () => {
);
// Close the context menu if it's open whenever the window is clicked.
- const onPaneClick = useCallback(() => setMenu(null), [setMenu]);
- const onNodeClick = useCallback((event, node) => {
- if (lastClicked.type == 'exponent' && (node.type == "numeric" || node.type == "variable")) { // changing/setting exponent node location and data
+ const onPaneClick = useCallback(() => setMenu(null), [setMenu]);
+
+ //this onNodeClick function handles custom click functionality for nodes (exponentnode)
+ const onNodeClick = useCallback((event, node) => {
+ console.log("Node clicked");
+ if (isDeleting) {
+ deleteElements({ nodes: [{ id: node.id }] });
+ return null;
+ }
+ //checks if you click an exponentnode and then you click a numeric or variable node to snap the exponentnode to the other node
+ if (lastClicked.type == 'exponent' && (node.type == "numeric" || node.type == "variable")) {
+
+
setNodes((nds) => //this basically runs through every node and sets its exponentConnection to an empty string except for the one that just got connected
//(this is really weird so dont touch)
nds.map((node) => {
@@ -135,43 +155,44 @@ const Flow = () => {
})
);
+ // changing/setting exponent node location and data (lastClicked is the exponentnode, node is the most recently clicked node)
lastClicked.position.x = node.position.x + 50;
lastClicked.position.y = node.position.y - 25;
node.data.exponentConnection = lastClicked.id;
}
- lastClicked = node;
+ lastClicked = node; //sets the lastClicked node to the actual last clicked node
}, []);
// Handles deleting an edge by connecting the incomers and outgoers and deleting edges to the nodes
const onNodesDelete = useCallback((deleted) => {
- deleted.forEach((nd) => { //for each node(nd)
+ deleted.forEach((nd) => { // Anything but a for loop
if (nd.type == 'connector') // Skips if the node is a connector (this would cause an error)
return;
- if (nd.data.connectors.upper != '') // Deletes connectors to a node
+ if (nd.data.connectors.upper != '') // Removes connection data from node
deleteElements({ nodes: [{ id: nd.data.connectors.upper }] });
if (nd.data.connectors.lower != '')
deleteElements({ nodes: [{ id: nd.data.connectors.lower }] });
- setNodes((nds) =>
+ setNodes((nds) => // Removes nodes in deleted from the nodes array and any connectors attached to those nodes
nds.filter((node) => {
return !(node === nd || (node.type == 'connector' && node.data.origin == nd.id));
})
);
});
- deleted.forEach((nd) => {
- if (source1Types.includes(nd.type) && nd.rightConnection != '')
- getNode(id).leftConnection = '';
- if (source2Types.includes(nd.type) && nd.leftConnection != '')
- getNode(id).rightConnection = '';
- if (target2Types.includes(nd.type) && nd.upperConnection != '')
- getNode(id).lowerConnection = '';
- if (target1Types.includes(nd.type) && nd.lowerConnection != '')
- getNode(id).upperConnection = '';
+ deleted.forEach((nd) => { // Removes connections of nodes that were previously attached to the deleted node
+ if (source1Types.includes(nd.type) && nd.rightNode != '')
+ getNode(id).leftNode = '';
+ if (source2Types.includes(nd.type) && nd.leftNode != '')
+ getNode(id).rightNode = '';
+ if (target2Types.includes(nd.type) && nd.upperNode != '')
+ getNode(id).lowerNode = '';
+ if (target1Types.includes(nd.type) && nd.loweNode != '')
+ getNode(id).upperNode = '';
});
setEdges(deleted.reduce((acc, node) =>
@@ -207,16 +228,16 @@ const Flow = () => {
const store = useStoreApi();
const { getInternalNode } = useReactFlow();
- // Proximity Connect
+ // Proximity Connect (this needs some heavy reworking to fix bugs and minimize interference between nodes)
const getClosestEdge = useCallback((node) => {
const { nodeLookup } = store.getState();
const internalNode = getInternalNode(node.id); // Node being used
const validNodes = Array.from(nodeLookup.values()).filter(n => {
- if (n.type === 'connector') {
- return n.origin === internalNode.id || n.internals.positionAbsolute.x > internalNode.internals.positionAbsolute.x;
- } //^^makes sure that a connector node doesnt try to connect to its linked fraction node
+ //if (n.type === 'connector') {
+ // return n.origin === internalNode.id || n.internals.positionAbsolute.x > internalNode.internals.positionAbsolute.x;
+ //} //^^makes sure that a connector node doesnt try to connect to its linked parent node
return n.id !== internalNode.id;
})
@@ -270,8 +291,8 @@ const Flow = () => {
);
};
- const handleExists = (node, handleSuffix) => {
- if (handleSuffix == "_source1")
+ const handleExists = (node, handleSuffix) => { // Helper function to check if a node has a certain type of handle (left right upper lower).
+ if (handleSuffix == "_source1") // Uses arrays from near the top of the file to check if the node type should have the handle
return source1Types.includes(node.type);
if (handleSuffix == "_source2")
return source2Types.includes(node.type);
@@ -345,9 +366,13 @@ const Flow = () => {
}, [edges.filter((e) => e.className !== 'temp').length]); // The one dependency for this useCallback is the number of non-temporary edges
-
+ const updateFractionEvent = new Event('updateFraction');
const onNodeDrag = useCallback(
- (_, node) => {
+ (_, node) => {
+ console.log(node.id);
+
+ document.dispatchEvent(updateFractionEvent);
+
if (node.data.exponentConnection != '') //moving exponent nodes with its coefficient
{
moveNode(node.data.exponentConnection, node.position.x + EXP_POSITION_REL.x, node.position.y + EXP_POSITION_REL.y);
@@ -540,10 +565,10 @@ const Flow = () => {
[getClosestEdge],
);
- // DnD Implementation
+ // The rest of this component is the system to create nodes using the drag and drop system. A lot of this will need to be overhauled when we switch from HTML drag and drop to React DnD.
const reactFlowWrapper = useRef(null);
const { screenToFlowPosition } = useReactFlow();
- const [type] = useType(); // Context that gives type of currently dragged node
+ const [type] = useType(); // Context that gives type of the node currently being dragged
const [latexEq] = useLatexEq(); // Context that gives LaTeX equation of currently dragged node (Both provided by sidebar)
const onDragOver = useCallback( // Graphic effect for when node is dragged over viewport
@@ -551,40 +576,42 @@ const Flow = () => {
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
}, []);
-
- const onDrop = useCallback((event) => { // When node is dropped onto the viewport
- event.preventDefault();
- if (!type) { // Return if type is null
- return;
- }
+ const onDrop = useCallback((event) => { // Activates when a node is dropped onto the viewport
+
+ //event.preventDefault();
- if (!latexEq && type !== 'output') { // Special case for the unused output node
- return;
- }
+ //if (!type) { // End the function if type is null
+ // return;
+ //}
- const position = screenToFlowPosition({
- x: event.clientX,
- y: event.clientY,
- });
+ //if (!latexEq && type !== 'output') { // Special case for the unused output node
+ // return;
+ //}
+
+ //const position = screenToFlowPosition({ // Converts the monitor screen position to the position used on the react flow viewport
+ // x: event.clientX,
+ // y: event.clientY,
+ //});
const newNode = { // Creates new node using position and contexts
id: getId(),
- type,
- position,
- data: { value: `${latexEq}`, label: `${latexEq}`,
- group: groupNum, row: rowNum, col: colNum,
- leftNode: '', rightNode: '', upperNode: '', lowerNode: '', exponentConnection: '',
- connectors: {upper: '', lower: ''}
+ type: event.detail.type,
+ position: {x: event.detail.position.x, y: event.detail.position.y},
+ data: { value: `${event.detail.latexEq}`, label: `${event.detail.latexEq}`, // Internal value and label, both in LaTeX
+ group: groupNum, row: rowNum, col: colNum, // Old system for keeping track of node connections. Will eventually be removed
+ leftNode: '', rightNode: '', upperNode: '', lowerNode: '', exponentConnection: '', // New system for keeping track of node connections. Each one is an id for a connecrted node. Lower and upper connection will eventually be removed in favor of vertical connectors
+ connectors: {upper: '', lower: ''} // Vertical connectors for specific nodes (for example, fractions). Not used in every node
}
};
-
- if (upperConnectionTypes.includes(newNode.type))
+
+ //Creation of vertical connectors if needed
+ if (upperConnectionTypes.includes(newNode.type)) // Checks array of all node types that need upper connectors (found near top of app file)
{
- const connectorPosition = {x: newNode.position.x + UPPER_POSITION_REL.x, y:newNode.position.y + UPPER_POSITION_REL.y}
+ const connectorPosition = {x: newNode.position.x + UPPER_POSITION_REL.x, y:newNode.position.y + UPPER_POSITION_REL.y} // Position for new connector, offset from node position
- const newConnector =
- {
+ const newConnector = // Creation is similar to above. Connectors have less data since they only need one connection (right)
+ { // and they are undraggable and undeletable (since they follow their parent node)
id: getId(),
type: 'connector',
position: connectorPosition,
@@ -595,11 +622,11 @@ const Flow = () => {
};
- setNodes((nds) => nds.concat(newConnector));
- newNode.data.connectors.upper = newConnector.id;
+ setNodes((nds) => nds.concat(newConnector)); // Setting state of nodes to include connector
+ newNode.data.connectors.upper = newConnector.id; // Establishing the new connector as the original node's upper connector
}
- if (lowerConnectionTypes.includes(newNode.type)) {
+ if (lowerConnectionTypes.includes(newNode.type)) { // Exact same thing as upper connector, just using a different reference array and a different offset
const connectorPosition = { x: newNode.position.x + LOWER_POSITION_REL.x, y: newNode.position.y + LOWER_POSITION_REL.y }
const newConnector =
@@ -621,27 +648,42 @@ const Flow = () => {
groupNum += 1;
- setNodes((nds) => nds.concat(newNode)); // Adds new node to nodes
- },
+ setNodes((nds) => nds.concat(newNode)); // Setting state of nodes to include the new node
+ },
[screenToFlowPosition, type, latexEq], // dependencies for UseCallback()
);
+ const startOrStopDeleting = useCallback((event) => {
+ isDeleting = !isDeleting;
+ console.log("A " + isDeleting);
+ },
+ [screenToFlowPosition, type, latexEq],
+ );
- const onDragStart = (event, nodeType, nodeLatexEq) => {
- setType(nodeType);
- setLatexEq(nodeLatexEq);
- event.dataTransfer.setData('text/plain', nodeType);
- event.dataTransfer.setData('text/plain', nodeLatexEq);
- event.dataTransfer.effectAllow = 'move';
- };
+
+ document.addEventListener("trashClicked", startOrStopDeleting);
+
+
+
+ document.addEventListener("drop", onDrop);
+
+ //const onDragStart = (event, nodeType, nodeLatexEq) => {
+ // setType(nodeType);
+ // setLatexEq(nodeLatexEq);
+ // event.dataTransfer.setData('text/plain', nodeType);
+ // event.dataTransfer.setData('text/plain', nodeLatexEq);
+ // event.dataTransfer.effectAllow = 'move';
+ //};
// Final return for