diff --git a/cypress/e2e/DoenetML/tagSpecific/callaction.cy.js b/cypress/e2e/DoenetML/tagSpecific/callaction.cy.js index 41c4006396..4e368bbc8c 100644 --- a/cypress/e2e/DoenetML/tagSpecific/callaction.cy.js +++ b/cypress/e2e/DoenetML/tagSpecific/callaction.cy.js @@ -1285,6 +1285,146 @@ describe('CallAction Tag Tests', function () { }) + it('action triggered when mouse down', () => { + + cy.window().then(async (win) => { + win.postMessage({ + doenetML: ` + a + + (-1,2) + + + +

+

+ +

+ `}, "*"); + }); + + cy.get('#\\/_text1').should('have.text', 'a') //wait for page to load + + let numbers; + + cy.get('#\\/nums').invoke('text').then(text => { + numbers = text.split(',').map(Number); + expect(numbers.length).eq(5); + for (let num of numbers) { + expect(Number.isInteger(num)).be.true; + expect(num).gte(1) + expect(num).lte(6) + } + }) + + cy.get('#\\/P2').should('contain.text', '(−1,2)') + + cy.get('#\\/rs').should('not.exist'); + + cy.window().then(async (win) => { + await win.callAction1({ + actionName: "movePoint", + componentName: "/P", + args: { x: 3, y: -4 } + }); + cy.get('#\\/P2').should('contain.text', '(3,−4)') + + cy.get('#\\/nums').invoke('text').then(text => { + let numbers2 = text.split(',').map(Number); + expect(numbers2).eqls(numbers) + }); + }) + + cy.window().then(async (win) => { + await win.callAction1({ + actionName: "mouseDownOnPoint", + componentName: "/P", + }); + + cy.waitUntil(() => cy.get('#\\/nums').invoke('text').then(text => { + let numbers2 = text.split(',').map(Number); + if (numbers2.length !== 5) { + return false; + } + let foundChange = false; + for (let [i, num] of numbers2.entries()) { + if (!Number.isInteger(num) || num < 1 || num > 6) { + return false; + } + if (num !== numbers[i]) { + foundChange = true; + } + } + if (!foundChange) { + return false; + } + numbers = numbers2; + return true; + })) + + }) + + cy.window().then(async (win) => { + await win.callAction1({ + actionName: "movePoint", + componentName: "/P", + args: { x: 5, y: 9 } + }); + + cy.get('#\\/P2').should('contain.text', '(5,9)') + + cy.get('#\\/nums').invoke('text').then(text => { + let numbers2 = text.split(',').map(Number); + expect(numbers2).eqls(numbers) + }); + }) + + + cy.window().then(async (win) => { + await win.callAction1({ + actionName: "mouseDownOnPoint", + componentName: "/P", + }); + + cy.waitUntil(() => cy.get('#\\/nums').invoke('text').then(text => { + let numbers2 = text.split(',').map(Number); + if (numbers2.length !== 5) { + return false; + } + let foundChange = false; + for (let [i, num] of numbers2.entries()) { + if (!Number.isInteger(num) || num < 1 || num > 6) { + return false; + } + if (num !== numbers[i]) { + foundChange = true; + } + } + if (!foundChange) { + return false; + } + numbers = numbers2; + return true; + })) + }) + + cy.window().then(async (win) => { + await win.callAction1({ + actionName: "movePoint", + componentName: "/P", + args: { x: 9, y: 7 } + }); + + cy.get('#\\/P2').should('contain.text', '(9,7)') + + cy.get('#\\/nums').invoke('text').then(text => { + let numbers2 = text.split(',').map(Number); + expect(numbers2).eqls(numbers) + }); + + }); + }) + it('chained updates based on trigger', () => { cy.window().then(async (win) => { diff --git a/cypress/e2e/DoenetML/tagSpecific/triggerset.cy.js b/cypress/e2e/DoenetML/tagSpecific/triggerset.cy.js index eddb749954..5302ac05dc 100644 --- a/cypress/e2e/DoenetML/tagSpecific/triggerset.cy.js +++ b/cypress/e2e/DoenetML/tagSpecific/triggerset.cy.js @@ -723,6 +723,110 @@ describe('TriggerSet Tag Tests', function () { }); }) + it('triggerSet triggered when mouse down', () => { + + cy.window().then(async (win) => { + win.postMessage({ + doenetML: ` + a + + (-1,2) + + x + y + + + + + + `}, "*"); + }); + cy.get('#\\/_text1').should('have.text', 'a') //wait for page to load + + cy.get('#\\/x').find('.mjx-mrow').eq(0).invoke('text').then((text) => { + expect(text.trim()).equal('x') + }); + cy.get('#\\/y').find('.mjx-mrow').eq(0).invoke('text').then((text) => { + expect(text.trim()).equal('y') + }); + + cy.get('#\\/trip').should('not.exist'); + cy.get('#\\/quad').should('not.exist'); + + cy.window().then(async (win) => { + await win.callAction1({ + actionName: "movePoint", + componentName: "/P", + args: { x: -1, y: -7 } + }); + cy.get('#\\/x').find('.mjx-mrow').eq(0).invoke('text').then((text) => { + expect(text.trim()).equal('x') + }); + cy.get('#\\/y').find('.mjx-mrow').eq(0).invoke('text').then((text) => { + expect(text.trim()).equal('y') + }); + }) + + + cy.window().then(async (win) => { + await win.callAction1({ + actionName: "mouseDownOnPoint", + componentName: "/P", + }); + cy.get('#\\/x').should('contain.text', '3x') + cy.get('#\\/x').find('.mjx-mrow').eq(0).invoke('text').then((text) => { + expect(text.trim()).equal('3x') + }); + cy.get('#\\/y').find('.mjx-mrow').eq(0).invoke('text').then((text) => { + expect(text.trim()).equal('4y') + }); + }) + + cy.window().then(async (win) => { + await win.callAction1({ + actionName: "movePoint", + componentName: "/P", + args: { x: 5, y: 9 } + }); + cy.get('#\\/x').find('.mjx-mrow').eq(0).invoke('text').then((text) => { + expect(text.trim()).equal('3x') + }); + cy.get('#\\/y').find('.mjx-mrow').eq(0).invoke('text').then((text) => { + expect(text.trim()).equal('4y') + }); + }) + + cy.window().then(async (win) => { + await win.callAction1({ + actionName: "mouseDownOnPoint", + componentName: "/P", + }); + cy.get('#\\/x').should('contain.text', '9x') + cy.get('#\\/x').find('.mjx-mrow').eq(0).invoke('text').then((text) => { + expect(text.trim()).equal('9x') + }); + cy.get('#\\/y').find('.mjx-mrow').eq(0).invoke('text').then((text) => { + expect(text.trim()).equal('16y') + }); + }) + + cy.window().then(async (win) => { + await win.callAction1({ + actionName: "movePoint", + componentName: "/P", + args: { x: 9, y: 7 } + }); + cy.get('#\\/x').find('.mjx-mrow').eq(0).invoke('text').then((text) => { + expect(text.trim()).equal('9x') + }); + cy.get('#\\/y').find('.mjx-mrow').eq(0).invoke('text').then((text) => { + expect(text.trim()).equal('16y') + }); + + + }); + }) + it('triggerWhen supercedes chaining for triggerset', () => { cy.window().then(async (win) => { diff --git a/cypress/e2e/DoenetML/tagSpecific/updatevalue.cy.js b/cypress/e2e/DoenetML/tagSpecific/updatevalue.cy.js index bd0b706aa5..0d82e5b77e 100644 --- a/cypress/e2e/DoenetML/tagSpecific/updatevalue.cy.js +++ b/cypress/e2e/DoenetML/tagSpecific/updatevalue.cy.js @@ -826,6 +826,86 @@ describe('UpdateValue Tag Tests', function () { }); }) + it('update triggered when mouse down', () => { + + cy.window().then(async (win) => { + win.postMessage({ + doenetML: ` + a + + (-1,2) + + x + + + `}, "*"); + }); + cy.get('#\\/_text1').should('have.text', 'a') //wait for page to load + + cy.get('#\\/x').find('.mjx-mrow').eq(0).invoke('text').then((text) => { + expect(text.trim()).equal('x') + }) + + cy.get('#\\/trip').should('not.exist'); + + cy.window().then(async (win) => { + await win.callAction1({ + actionName: "movePoint", + componentName: "/P", + args: { x: -1, y: -7 } + }); + cy.get('#\\/x').find('.mjx-mrow').eq(0).invoke('text').then((text) => { + expect(text.trim()).equal('x') + }); + }) + + cy.window().then(async (win) => { + await win.callAction1({ + actionName: "mouseDownOnPoint", + componentName: "/P", + }); + cy.get('#\\/x').should('contain.text', '3x') + cy.get('#\\/x').find('.mjx-mrow').eq(0).invoke('text').then((text) => { + expect(text.trim()).equal('3x') + }); + }) + + cy.window().then(async (win) => { + await win.callAction1({ + actionName: "movePoint", + componentName: "/P", + args: { x: 5, y: 9 } + }); + cy.get('#\\/x').find('.mjx-mrow').eq(0).invoke('text').then((text) => { + expect(text.trim()).equal('3x') + }); + }) + + cy.window().then(async (win) => { + await win.callAction1({ + actionName: "mouseDownOnPoint", + componentName: "/P", + }); + cy.get('#\\/x').should('contain.text', '9x') + cy.get('#\\/x').find('.mjx-mrow').eq(0).invoke('text').then((text) => { + expect(text.trim()).equal('9x') + }); + }) + + cy.window().then(async (win) => { + await win.callAction1({ + actionName: "movePoint", + componentName: "/P", + args: { x: 9, y: 7 } + }); + cy.get('#\\/x').find('.mjx-mrow').eq(0).invoke('text').then((text) => { + expect(text.trim()).equal('9x') + }); + + + }); + }) + it('chained updates based on trigger', () => { cy.window().then(async (win) => { diff --git a/src/Core/components/Circle.js b/src/Core/components/Circle.js index b0c5b3078d..2f9f69b6e1 100644 --- a/src/Core/components/Circle.js +++ b/src/Core/components/Circle.js @@ -11,6 +11,7 @@ export default class Circle extends Curve { actions = { moveCircle: this.moveCircle.bind(this), circleClicked: this.circleClicked.bind(this), + mouseDownOnCircle: this.mouseDownOnCircle.bind(this), }; @@ -2417,6 +2418,17 @@ export default class Circle extends Curve { this.coreFunctions.resolveAction({ actionId }); } + + async mouseDownOnCircle({ actionId }) { + + await this.coreFunctions.triggerChainedActions({ + triggeringAction: "down", + componentName: this.componentName, + }) + + this.coreFunctions.resolveAction({ actionId }); + + } } function circleFromTwoNumericalPoints({ point1, point2 }) { diff --git a/src/Core/components/Curve.js b/src/Core/components/Curve.js index d1b343ae8f..298e3fe174 100644 --- a/src/Core/components/Curve.js +++ b/src/Core/components/Curve.js @@ -16,7 +16,8 @@ export default class Curve extends GraphicalComponent { moveThroughPoint: this.moveThroughPoint.bind(this), changeVectorControlDirection: this.changeVectorControlDirection.bind(this), switchCurve: this.switchCurve.bind(this), - curveClicked: this.curveClicked.bind(this) + curveClicked: this.curveClicked.bind(this), + mouseDownOnCurve: this.mouseDownOnCurve.bind(this), }; static primaryStateVariableForDefinition = "fShadow"; @@ -3287,6 +3288,17 @@ export default class Curve extends GraphicalComponent { } + async mouseDownOnCurve({ actionId, name }) { + + await this.coreFunctions.triggerChainedActions({ + triggeringAction: "down", + componentName: name, // use name rather than this.componentName to get original name if adapted + }) + + this.coreFunctions.resolveAction({ actionId }); + + } + } function getNearestPointFunctionCurve({ dependencyValues, numerics }) { diff --git a/src/Core/components/Line.js b/src/Core/components/Line.js index 331ad80626..bd7a8987f3 100644 --- a/src/Core/components/Line.js +++ b/src/Core/components/Line.js @@ -8,7 +8,8 @@ export default class Line extends GraphicalComponent { actions = { moveLine: this.moveLine.bind(this), switchLine: this.switchLine.bind(this), - lineClicked: this.lineClicked.bind(this) + lineClicked: this.lineClicked.bind(this), + mouseDownOnLine: this.mouseDownOnLine.bind(this), }; @@ -1689,6 +1690,17 @@ export default class Line extends GraphicalComponent { } + async mouseDownOnLine({ actionId }) { + + await this.coreFunctions.triggerChainedActions({ + triggeringAction: "down", + componentName: this.componentName, + }) + + this.coreFunctions.resolveAction({ actionId }); + + } + } diff --git a/src/Core/components/LineSegment.js b/src/Core/components/LineSegment.js index 9583301743..1125ffdc0d 100644 --- a/src/Core/components/LineSegment.js +++ b/src/Core/components/LineSegment.js @@ -8,6 +8,7 @@ export default class LineSegment extends GraphicalComponent { actions = { moveLineSegment: this.moveLineSegment.bind(this), lineSegmentClicked: this.lineSegmentClicked.bind(this), + mouseDownOnLineSegment: this.mouseDownOnLineSegment.bind(this), }; static createAttributesObject() { @@ -581,4 +582,15 @@ export default class LineSegment extends GraphicalComponent { } + async mouseDownOnLineSegment({ actionId }) { + + await this.coreFunctions.triggerChainedActions({ + triggeringAction: "down", + componentName: this.componentName, + }) + + this.coreFunctions.resolveAction({ actionId }); + + } + } \ No newline at end of file diff --git a/src/Core/components/Polygon.js b/src/Core/components/Polygon.js index 96c9f1cd7e..bed1884051 100644 --- a/src/Core/components/Polygon.js +++ b/src/Core/components/Polygon.js @@ -6,7 +6,8 @@ export default class Polygon extends Polyline { actions = { movePolygon: this.movePolygon.bind(this), - polygonClicked: this.polygonClicked.bind(this) + polygonClicked: this.polygonClicked.bind(this), + mouseDownOnPolygon: this.mouseDownOnPolygon.bind(this), }; get movePolygon() { @@ -17,6 +18,10 @@ export default class Polygon extends Polyline { return this.polylineClicked; } + get mouseDownOnPolygon() { + return this.mouseDownOnPolyline; + } + static createAttributesObject() { let attributes = super.createAttributesObject(); diff --git a/src/Core/components/Polyline.js b/src/Core/components/Polyline.js index 7513192a13..c010edad27 100644 --- a/src/Core/components/Polyline.js +++ b/src/Core/components/Polyline.js @@ -7,7 +7,8 @@ export default class Polyline extends GraphicalComponent { actions = { movePolyline: this.movePolyline.bind(this), finalizePolylinePosition: this.finalizePolylinePosition.bind(this), - polylineClicked: this.polylineClicked.bind(this) + polylineClicked: this.polylineClicked.bind(this), + mouseDownOnPolyline: this.mouseDownOnPolyline.bind(this), }; static createAttributesObject() { @@ -598,4 +599,15 @@ export default class Polyline extends GraphicalComponent { } + async mouseDownOnPolyline({ actionId }) { + + await this.coreFunctions.triggerChainedActions({ + triggeringAction: "down", + componentName: this.componentName, + }) + + this.coreFunctions.resolveAction({ actionId }); + + } + } \ No newline at end of file diff --git a/src/Core/components/Ray.js b/src/Core/components/Ray.js index d247f18f57..7c2910bae6 100644 --- a/src/Core/components/Ray.js +++ b/src/Core/components/Ray.js @@ -8,6 +8,7 @@ export default class Ray extends GraphicalComponent { actions = { moveRay: this.moveRay.bind(this), rayClicked: this.rayClicked.bind(this), + mouseDownOnRay: this.mouseDownOnRay.bind(this), }; static createAttributesObject() { @@ -1494,5 +1495,16 @@ export default class Ray extends GraphicalComponent { } + async mouseDownOnRay({ actionId }) { + + await this.coreFunctions.triggerChainedActions({ + triggeringAction: "down", + componentName: this.componentName, + }) + + this.coreFunctions.resolveAction({ actionId }); + + } + } \ No newline at end of file diff --git a/src/Core/components/Vector.js b/src/Core/components/Vector.js index f264b0cecd..ff69e02fcf 100644 --- a/src/Core/components/Vector.js +++ b/src/Core/components/Vector.js @@ -9,6 +9,7 @@ export default class Vector extends GraphicalComponent { actions = { moveVector: this.moveVector.bind(this), vectorClicked: this.vectorClicked.bind(this), + mouseDownOnVector: this.mouseDownOnVector.bind(this), } static primaryStateVariableForDefinition = "displacementShadow"; @@ -2090,4 +2091,15 @@ export default class Vector extends GraphicalComponent { } + async mouseDownOnVector({ actionId }) { + + await this.coreFunctions.triggerChainedActions({ + triggeringAction: "down", + componentName: this.componentName, + }) + + this.coreFunctions.resolveAction({ actionId }); + + } + } \ No newline at end of file diff --git a/src/Viewer/renderers/circle.jsx b/src/Viewer/renderers/circle.jsx index 5d2e7b4f17..79e246b3fb 100644 --- a/src/Viewer/renderers/circle.jsx +++ b/src/Viewer/renderers/circle.jsx @@ -142,6 +142,9 @@ export default React.memo(function Circle(props) { centerAtDown.current = [...newCircleJXG.center.coords.scrCoords]; radiusAtDown.current = newCircleJXG.radius; throughAnglesAtDown.current = [...throughAnglesFromCore.current]; + callAction({ + action: actions.mouseDownOnCircle + }); }); previousWithLabel.current = SVs.showLabel && SVs.labelForGraph !== ""; diff --git a/src/Viewer/renderers/curve.jsx b/src/Viewer/renderers/curve.jsx index 69c36a0a29..81e874817a 100644 --- a/src/Viewer/renderers/curve.jsx +++ b/src/Viewer/renderers/curve.jsx @@ -204,7 +204,14 @@ export default React.memo(function Curve(props) { if (SVs.curveType === "bezier") { board.on('up', upBoard); - newCurveJXG.on('down', downOther); + newCurveJXG.on('down', () => { + downOther(); + callAction({ + action: actions.mouseDownOnCurve, + args: { name } // send name so get original name if adapted + }); + + }); segmentAttributes.current = { visible: false, @@ -271,6 +278,10 @@ export default React.memo(function Curve(props) { } else { newCurveJXG.on('down', function (e) { updateSinceDown.current = false; + callAction({ + action: actions.mouseDownOnCurve, + args: { name } // send name so get original name if adapted + }); }); } return newCurveJXG; @@ -279,6 +290,7 @@ export default React.memo(function Curve(props) { function deleteCurveJXG() { board.off('up', upBoard); curveJXG.current.off('down'); + curveJXG.current.off('up'); board.removeObject(curveJXG.current); curveJXG.current = null; deleteControls(); diff --git a/src/Viewer/renderers/line.jsx b/src/Viewer/renderers/line.jsx index b4fd556dbd..af07266550 100644 --- a/src/Viewer/renderers/line.jsx +++ b/src/Viewer/renderers/line.jsx @@ -174,6 +174,9 @@ export default React.memo(function Line(props) { [...newLineJXG.point1.coords.scrCoords], [...newLineJXG.point2.coords.scrCoords] ] + callAction({ + action: actions.mouseDownOnLine + }); }) diff --git a/src/Viewer/renderers/lineSegment.jsx b/src/Viewer/renderers/lineSegment.jsx index 834ad887cd..311a6fe023 100644 --- a/src/Viewer/renderers/lineSegment.jsx +++ b/src/Viewer/renderers/lineSegment.jsx @@ -197,11 +197,17 @@ export default React.memo(function LineSegment(props) { draggedPoint.current = null; pointerAtDown.current = [e.x, e.y]; downOnPoint.current = 1; + callAction({ + action: actions.mouseDownOnLineSegment + }); }); point2JXG.current.on('down', (e) => { draggedPoint.current = null; pointerAtDown.current = [e.x, e.y]; downOnPoint.current = 2; + callAction({ + action: actions.mouseDownOnLineSegment + }); }); lineSegmentJXG.current.on('down', function (e) { draggedPoint.current = null; @@ -210,6 +216,12 @@ export default React.memo(function LineSegment(props) { [...point1JXG.current.coords.scrCoords], [...point2JXG.current.coords.scrCoords] ] + if (downOnPoint.current === null) { + // Note: counting on fact that down on line segment itself will trigger after down on points + callAction({ + action: actions.mouseDownOnLineSegment + }); + } }); previousLabelPosition.current = SVs.labelPosition; diff --git a/src/Viewer/renderers/polygon.jsx b/src/Viewer/renderers/polygon.jsx index 8058e4ccab..e8de1ea6d3 100644 --- a/src/Viewer/renderers/polygon.jsx +++ b/src/Viewer/renderers/polygon.jsx @@ -131,6 +131,12 @@ export default React.memo(function Polygon(props) { pointsAtDown.current = newPolygonJXG.vertices.map(x => [...x.coords.scrCoords]) + if (downOnPoint.current === null) { + // Note: counting on fact that down on polygon itself will trigger after down on points + callAction({ + action: actions.mouseDownOnPolygon + }); + } }); @@ -155,6 +161,9 @@ export default React.memo(function Polygon(props) { draggedPoint.current = null; pointerAtDown.current = [e.x, e.y]; downOnPoint.current = i; + callAction({ + action: actions.mouseDownOnPolygon + }); }); } } diff --git a/src/Viewer/renderers/polyline.jsx b/src/Viewer/renderers/polyline.jsx index 931fc41f9b..176df4c87d 100644 --- a/src/Viewer/renderers/polyline.jsx +++ b/src/Viewer/renderers/polyline.jsx @@ -123,6 +123,9 @@ export default React.memo(function Polyline(props) { draggedPoint.current = null; pointerAtDown.current = [e.x, e.y]; downOnPoint.current = i; + callAction({ + action: actions.mouseDownOnPolyline + }); }); } @@ -135,6 +138,12 @@ export default React.memo(function Polyline(props) { pointsAtDown.current = newPolylineJXG.points.map(x => [...x.scrCoords]) + if (downOnPoint.current === null) { + // Note: counting on fact that down on polyline itself will trigger after down on points + callAction({ + action: actions.mouseDownOnPolyline + }); + } }); previousNVertices.current = SVs.nVertices; diff --git a/src/Viewer/renderers/ray.jsx b/src/Viewer/renderers/ray.jsx index 2d5fed71b2..37925f8e90 100644 --- a/src/Viewer/renderers/ray.jsx +++ b/src/Viewer/renderers/ray.jsx @@ -135,6 +135,9 @@ export default React.memo(function Ray(props) { [...newRayJXG.point1.coords.scrCoords], [...newRayJXG.point2.coords.scrCoords] ] + callAction({ + action: actions.mouseDownOnRay + }); }); diff --git a/src/Viewer/renderers/vector.jsx b/src/Viewer/renderers/vector.jsx index e322853e71..fc382d535c 100644 --- a/src/Viewer/renderers/vector.jsx +++ b/src/Viewer/renderers/vector.jsx @@ -173,12 +173,18 @@ export default React.memo(function Vector(props) { tailBeingDragged.current = false; pointerAtDown.current = [e.x, e.y]; downOnPoint.current = 1; + callAction({ + action: actions.mouseDownOnVector + }); }); newPoint2JXG.on('down', function (e) { headBeingDragged.current = false; tailBeingDragged.current = false; pointerAtDown.current = [e.x, e.y]; downOnPoint.current = 2; + callAction({ + action: actions.mouseDownOnVector + }); }); // if drag vector, need to keep track of original point positions @@ -191,6 +197,9 @@ export default React.memo(function Vector(props) { [...newVectorJXG.point1.coords.scrCoords], [...newVectorJXG.point2.coords.scrCoords] ]; + callAction({ + action: actions.mouseDownOnVector + }); }); function onDragHandler(e, i) {