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 + // Sidebar is at the top to pass contexts to the whole component. The React Flow component includes the viewport and all nodes/edges/connections. + // Within the React Flow component, we pass everything we created before in as features of the component. Below the React Flow component are various other React Flow and JS features and the Output Pane return ( -
- -
- + +
+ { onNodeDragStop={onNodeDragStop} onNodeClick = {onNodeClick} onDrop={onDrop} - onDragStart={onDragStart} onDragOver={onDragOver} ref={ref} onPaneClick={onPaneClick} onNodeContextMenu={onNodeContextMenu} defaultEdgeOptions={defaultEdgeOptions} - SelectionMode={SelectionMode.Partial} + selectionMode={SelectionMode.Partial} nodeTypes={nodeTypes} snapToGrid fitView className="reactflow-container" - > + > + Drag Blocks to Start Making Math! - {menu && }
- + +
); }; -// Exports flow (Entire above part of app.jsx) within necessary context providers - +// Exports flow (Entire above part of app.jsx) within necessary providers +// Providers exist solely to pass contexts down the the Flow component export default function App() { return (
@@ -691,4 +733,6 @@ export default function App() {
); -}; \ No newline at end of file +}; + +//haha i have captured sidd and am forcing him to work on mathnetic \ No newline at end of file diff --git a/src/components/SideBar/index.jsx b/src/Archived/oldIndex.jsx similarity index 88% rename from src/components/SideBar/index.jsx rename to src/Archived/oldIndex.jsx index 55c1c82..a0f53c2 100644 --- a/src/components/SideBar/index.jsx +++ b/src/Archived/oldIndex.jsx @@ -1,3 +1,13 @@ +//note: this sidebar works very differently from the one in the main branch + +//to change the size of the nodes in sidebar, go to src->components->context->index.css + +//index.css: +//100vh = 100% of the viewport height +//100vw = 100% of the viewport width +//100px = 100 pixels (dont use this, use vh or vw) +//overflow: scroll (changes sidebar to be able to be scrolled) + import React from 'react'; // import { useDnD } from '../DnDContext'; import { useEffect, useRef } from 'react'; @@ -21,7 +31,15 @@ export default () => { // Yes, I probably should have used a for loop. Shut up. // "I" + //im so sorry + // Drag and drop nodes, nodes in sidebar that are converted to actual nodes in app.jsx + + //classname should always be dndnode + //onDragStart(event, 'node type', 'symbol inside the node') + // ^ ^ ^ + // dont touch numeric,exponent,etc can use latex symbols (notation is '{\\name_of_symbol}') + return (