From 933f290061907fb206e7cd5ac8e463b821b46565 Mon Sep 17 00:00:00 2001 From: Emilio Alvarez Date: Fri, 24 Feb 2023 11:07:34 -0600 Subject: [PATCH 01/13] update dev container (#1951) * update to support git on linux * add github pr to extension list * add xml tools and gitlens --- .devcontainer/devcontainer.json | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 3ef07f8b44..1dd8a74154 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -31,7 +31,7 @@ "postStartCommand": "npx snowpack dev", // Uncomment to connect as a non-root user if you've added one. See https://aka.ms/vscode-remote/containers/non-root. - // "remoteUser": "vscode" + "remoteUser": "root", "customizations": { "vscode": { "extensions": [ @@ -39,10 +39,14 @@ "ms-azuretools.vscode-docker", "esbenp.prettier-vscode", "styled-components.vscode-styled-components", - "meganrogge.template-string-converter" + "meganrogge.template-string-converter", + "GitHub.vscode-pull-request-github", + "eamodio.gitlens", + "DotJoshJohnson.xml" ], "settings": { - "editor.formatOnSave": true + "editor.formatOnSave": true, + "extensions.verifySignature": false } } } From 34e5d68a0fd5431d7f8e2b4389f60da5be2a689b Mon Sep 17 00:00:00 2001 From: Duane Nykamp Date: Sat, 11 Feb 2023 12:25:18 -0600 Subject: [PATCH 02/13] createTargetComponentNames attribute type --- .../e2e/DoenetML/tagSpecific/callaction.cy.js | 407 +++++++++++++++++- src/Core/Core.js | 55 ++- src/Core/Dependencies.js | 101 ++--- src/Core/components/CallAction.js | 43 +- src/Core/components/CodeViewer.js | 33 +- src/Core/components/Constraints.js | 25 +- src/Core/components/Feedback.js | 72 ++-- src/Core/components/Label.js | 24 +- src/Core/components/Paginator.js | 33 +- src/Core/components/RenderDoenetML.js | 31 +- src/Core/components/SummaryStatistics.js | 24 +- src/Core/components/TriggerSet.js | 43 +- src/Core/components/UpdateValue.js | 43 +- src/Core/utils/serializedStateProcessing.js | 184 +++++--- 14 files changed, 755 insertions(+), 363 deletions(-) diff --git a/cypress/e2e/DoenetML/tagSpecific/callaction.cy.js b/cypress/e2e/DoenetML/tagSpecific/callaction.cy.js index 77c2d73391..41c4006396 100644 --- a/cypress/e2e/DoenetML/tagSpecific/callaction.cy.js +++ b/cypress/e2e/DoenetML/tagSpecific/callaction.cy.js @@ -438,6 +438,215 @@ describe('CallAction Tag Tests', function () { }) + it('chained actions, unnecessary $', () => { + + cy.window().then(async (win) => { + win.postMessage({ + doenetML: ` + a + +

+

+ +

+ + + (1,2) + + +

points from graph:

+ + + + (3,4) + + + `}, "*"); + }); + cy.get('#\\/_text1').should('have.text', 'a') //wait for page to load + + cy.get('#\\/addPoint_button').should("not.exist"); + + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + let g = stateVariables["/g"]; + + expect(g.stateValues.graphicalDescendants.length).eq(1); + + 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('#\\/rs_button').click(); + + cy.get('#\\/p2').should('contain.text', '(3,4'); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + let g = stateVariables["/g"]; + + let pointNames = g.stateValues.graphicalDescendants.map(x => x.componentName); + expect(pointNames.length).eq(2); + expect(stateVariables[pointNames[0]].stateValues.xs).eqls([1, 2]) + expect(stateVariables[pointNames[1]].stateValues.xs).eqls([3, 4]) + + + await win.callAction1({ + actionName: "movePoint", + componentName: pointNames[1], + args: { x: -2, y: 5 } + }) + + }); + + cy.get('#\\/p2').should('contain.text', '(−2,5)'); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + let g = stateVariables["/g"]; + + let pointNames = g.stateValues.graphicalDescendants.map(x => x.componentName); + expect(stateVariables[pointNames[1]].stateValues.xs).eqls([-2, 5]) + + + cy.get('#\\/nums').invoke('text').then(text => { + let numbers2 = text.split(',').map(Number); + expect(numbers2.length).eq(5); + for (let num of numbers2) { + expect(Number.isInteger(num)).be.true; + expect(num).gte(1) + expect(num).lte(6) + } + expect(numbers2).not.eqls(numbers) + }) + + }) + + }); + + }) + + it('chained actions, inside map', () => { + + cy.window().then(async (win) => { + win.postMessage({ + doenetML: ` + a + + + + + + + `}, "*"); + }); + cy.get('#\\/_text1').should('have.text', 'a') //wait for page to load\ + + + for (let ind = 1; ind <= 2; ind++) { + + cy.get(`#\\/set${ind}\\/addPoint_button`).should("not.exist"); + + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + let g = stateVariables[`/set${ind}/g`]; + + expect(g.stateValues.graphicalDescendants.length).eq(1); + + let numbers; + + cy.get(`#\\/set${ind}\\/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(`#\\/set${ind}\\/rs_button`).click(); + + cy.get(`#\\/set${ind}\\/p2`).should('contain.text', '(3,4'); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + let g = stateVariables[`/set${ind}/g`]; + + let pointNames = g.stateValues.graphicalDescendants.map(x => x.componentName); + expect(pointNames.length).eq(2); + expect(stateVariables[pointNames[0]].stateValues.xs).eqls([1, 2]) + expect(stateVariables[pointNames[1]].stateValues.xs).eqls([3, 4]) + + + await win.callAction1({ + actionName: "movePoint", + componentName: pointNames[1], + args: { x: -2, y: 5 } + }) + + }); + + cy.get(`#\\/set${ind}\\/p2`).should('contain.text', '(−2,5)'); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + let g = stateVariables[`/set${ind}/g`]; + + let pointNames = g.stateValues.graphicalDescendants.map(x => x.componentName); + expect(stateVariables[pointNames[1]].stateValues.xs).eqls([-2, 5]) + + + cy.get(`#\\/set${ind}\\/nums`).invoke('text').then(text => { + let numbers2 = text.split(',').map(Number); + expect(numbers2.length).eq(5); + for (let num of numbers2) { + expect(Number.isInteger(num)).be.true; + expect(num).gte(1) + expect(num).lte(6) + } + expect(numbers2).not.eqls(numbers) + }) + + }) + + }); + } + + }) + it('chained actions on multiple sources', () => { cy.window().then(async (win) => { @@ -656,19 +865,19 @@ describe('CallAction Tag Tests', function () { cy.waitUntil(() => cy.get('#\\/nums').invoke('text').then(text => { let numbers2 = text.split(',').map(Number); - if(numbers2.length !== 5) { + if (numbers2.length !== 5) { return false; } let foundChange = false; for (let [i, num] of numbers2.entries()) { - if(!Number.isInteger(num) || num < 1 || num > 6 ) { + if (!Number.isInteger(num) || num < 1 || num > 6) { return false; } - if(num !== numbers[i]) { + if (num !== numbers[i]) { foundChange = true; } } - if(!foundChange) { + if (!foundChange) { return false; } numbers = numbers2; @@ -733,19 +942,19 @@ describe('CallAction Tag Tests', function () { cy.waitUntil(() => cy.get('#\\/nums').invoke('text').then(text => { let numbers2 = text.split(',').map(Number); - if(numbers2.length !== 5) { + if (numbers2.length !== 5) { return false; } let foundChange = false; for (let [i, num] of numbers2.entries()) { - if(!Number.isInteger(num) || num < 1 || num > 6 ) { + if (!Number.isInteger(num) || num < 1 || num > 6) { return false; } - if(num !== numbers[i]) { + if (num !== numbers[i]) { foundChange = true; } } - if(!foundChange) { + if (!foundChange) { return false; } numbers = numbers2; @@ -828,19 +1037,19 @@ describe('CallAction Tag Tests', function () { cy.waitUntil(() => cy.get('#\\/nums').invoke('text').then(text => { let numbers2 = text.split(',').map(Number); - if(numbers2.length !== 5) { + if (numbers2.length !== 5) { return false; } let foundChange = false; for (let [i, num] of numbers2.entries()) { - if(!Number.isInteger(num) || num < 1 || num > 6 ) { + if (!Number.isInteger(num) || num < 1 || num > 6) { return false; } - if(num !== numbers[i]) { + if (num !== numbers[i]) { foundChange = true; } } - if(!foundChange) { + if (!foundChange) { return false; } numbers = numbers2; @@ -873,19 +1082,19 @@ describe('CallAction Tag Tests', function () { cy.waitUntil(() => cy.get('#\\/nums').invoke('text').then(text => { let numbers2 = text.split(',').map(Number); - if(numbers2.length !== 5) { + if (numbers2.length !== 5) { return false; } let foundChange = false; for (let [i, num] of numbers2.entries()) { - if(!Number.isInteger(num) || num < 1 || num > 6 ) { + if (!Number.isInteger(num) || num < 1 || num > 6) { return false; } - if(num !== numbers[i]) { + if (num !== numbers[i]) { foundChange = true; } } - if(!foundChange) { + if (!foundChange) { return false; } numbers = numbers2; @@ -910,6 +1119,172 @@ describe('CallAction Tag Tests', function () { }); }) + it('action triggered when click, inside template creating random names', () => { + + cy.window().then(async (win) => { + win.postMessage({ + doenetML: ` + a + + + + + `}, "*"); + }); + + cy.get('#\\/_text1').should('have.text', 'a') //wait for page to load + + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + for (let ind = 0; ind < 2; ind++) { + let templateName = stateVariables["/_map1"].replacements[ind].componentName; + + let tReps = stateVariables[templateName].replacements; + let graphName = tReps[1].componentName; + let copyName = tReps[3].componentName; + let numsAnchor = '#' + cesc(tReps[5].componentName); + + let PName = stateVariables[graphName].activeChildren[0].componentName; + let P2Anchor = '#' + cesc(stateVariables[copyName].replacements[0].componentName); + + + let numbers; + + cy.get(numsAnchor).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(P2Anchor).should('contain.text', '(−1,2)') + + + cy.window().then(async (win) => { + await win.callAction1({ + actionName: "movePoint", + componentName: PName, + args: { x: 3, y: -4 } + }); + cy.get(P2Anchor).should('contain.text', '(3,−4)') + + cy.get(numsAnchor).invoke('text').then(text => { + let numbers2 = text.split(',').map(Number); + expect(numbers2).eqls(numbers) + }); + }) + + cy.window().then(async (win) => { + await win.callAction1({ + actionName: "pointClicked", + componentName: PName, + }); + + cy.waitUntil(() => cy.get(numsAnchor).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: PName, + args: { x: 5, y: 9 } + }); + + cy.get(P2Anchor).should('contain.text', '(5,9)') + + cy.get(numsAnchor).invoke('text').then(text => { + let numbers2 = text.split(',').map(Number); + expect(numbers2).eqls(numbers) + }); + }) + + + cy.window().then(async (win) => { + await win.callAction1({ + actionName: "pointClicked", + componentName: PName, + }); + + cy.waitUntil(() => cy.get(numsAnchor).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: PName, + args: { x: 9, y: 7 } + }); + + cy.get(P2Anchor).should('contain.text', '(9,7)') + + cy.get(numsAnchor).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/src/Core/Core.js b/src/Core/Core.js index bfeb5dd2ed..36c68b2dab 100644 --- a/src/Core/Core.js +++ b/src/Core/Core.js @@ -426,11 +426,6 @@ export default class Core { return []; } - if (parent.isShadow) { - console.warn(`Cannot add children to parent ${parentName} as it is a shadow component.`); - return []; - } - ancestors = [ { componentName: parentName, @@ -1253,7 +1248,7 @@ export default class Core { }); } - + if (componentClass.keepChildrenSerialized) { let childrenAddressed = new Set([]); @@ -2610,12 +2605,10 @@ export default class Core { hasEssential: true, }; - let attributeFromPrimitive = !attributeSpecification.createComponentOfType; - if (attributeSpecification.public) { stateVarDef.public = true; stateVarDef.shadowingInstructions = {}; - if (attributeFromPrimitive) { + if (attributeSpecification.createPrimitiveOfType) { stateVarDef.shadowingInstructions.createComponentOfType = attributeSpecification.createPrimitiveOfType; if (stateVarDef.shadowingInstructions.createComponentOfType === "string") { stateVarDef.shadowingInstructions.createComponentOfType = "text"; @@ -2624,6 +2617,8 @@ export default class Core { } else if (stateVarDef.shadowingInstructions.createComponentOfType === "numberArray") { stateVarDef.shadowingInstructions.createComponentOfType = "numberList"; } + } else if (attributeSpecification.createTargetComponentNames) { + throw Error("Cannot make a public state variable from an attribute with createTargetComponentNames"); } else { stateVarDef.shadowingInstructions.createComponentOfType = attributeSpecification.createComponentOfType; } @@ -2631,7 +2626,7 @@ export default class Core { let stateVariableForAttributeValue; - if (!attributeFromPrimitive) { + if (attributeSpecification.createComponentOfType) { let attributeClass = this.componentInfoObjects.allComponentClasses[attributeSpecification.createComponentOfType]; if (!attributeClass) { @@ -2656,11 +2651,16 @@ export default class Core { variableName: attributeSpecification.fallBackToParentStateVariable } } - if (attributeFromPrimitive) { + if (attributeSpecification.createPrimitiveOfType) { dependencies.attributePrimitive = { dependencyType: "attributePrimitive", attributeName: attrName } + } else if (attributeSpecification.createTargetComponentNames) { + dependencies.attributeTargetComponentNames = { + dependencyType: "attributeTargetComponentNames", + attributeName: attrName + } } else { dependencies.attributeComponent = { dependencyType: "attributeComponent", @@ -2682,6 +2682,10 @@ export default class Core { && dependencyValues.attributePrimitive !== null ) { attributeValue = dependencyValues.attributePrimitive; + } else if (dependencyValues.attributeTargetComponentNames !== undefined + && dependencyValues.attributeTargetComponentNames !== null + ) { + attributeValue = dependencyValues.attributeTargetComponentNames; } else { // parentValue would be undefined if fallBackToParentStateVariable wasn't specified @@ -2718,6 +2722,10 @@ export default class Core { // can't invert if have primitive return { success: false } } + if (dependencyValues.attributeTargetComponentNames !== undefined && dependencyValues.attributeTargetComponentNames !== null) { + // can't invert if have target component names + return { success: false } + } let haveParentValue = dependencyValues.parentValue !== undefined && dependencyValues.parentValue !== null; @@ -2801,12 +2809,10 @@ export default class Core { hasEssential: true, }; - let attributeFromPrimitive = !attributeSpecification.createComponentOfType; - if (attributeSpecification.public) { stateVarDef.public = true; stateVarDef.shadowingInstructions = {}; - if (attributeFromPrimitive) { + if (attributeSpecification.createPrimitiveOfType) { stateVarDef.shadowingInstructions.createComponentOfType = attributeSpecification.createPrimitiveOfType; if (stateVarDef.shadowingInstructions.createComponentOfType === "string") { stateVarDef.shadowingInstructions.createComponentOfType = "text"; @@ -2815,6 +2821,8 @@ export default class Core { } else if (stateVarDef.shadowingInstructions.createComponentOfType === "numberArray") { stateVarDef.shadowingInstructions.createComponentOfType = "numberList"; } + } else if (attributeSpecification.createTargetComponentNames) { + throw Error("Cannot make a public state variable from an attribute with createTargetComponentNames"); } else { stateVarDef.shadowingInstructions.createComponentOfType = attributeSpecification.createComponentOfType; } @@ -2989,7 +2997,7 @@ export default class Core { if (attributeSpecification.public) { stateVarDef.public = true; stateVarDef.shadowingInstructions = {}; - if (attributeFromPrimitive) { + if (attributeSpecification.createPrimitiveOfType) { stateVarDef.shadowingInstructions.createComponentOfType = attributeSpecification.createPrimitiveOfType; if (stateVarDef.shadowingInstructions.createComponentOfType === "string") { stateVarDef.shadowingInstructions.createComponentOfType = "text"; @@ -2998,6 +3006,8 @@ export default class Core { } else if (stateVarDef.shadowingInstructions.createComponentOfType === "numberArray") { stateVarDef.shadowingInstructions.createComponentOfType = "numberList"; } + } else if (attributeSpecification.createTargetComponentNames) { + throw Error("Cannot make a public state variable from an attribute with createTargetComponentNames"); } else { stateVarDef.shadowingInstructions.createComponentOfType = attributeSpecification.createComponentOfType; } @@ -3006,7 +3016,7 @@ export default class Core { let stateVariableForAttributeValue; - if (!attributeFromPrimitive) { + if (attributeSpecification.createComponentOfType) { let attributeClass = this.componentInfoObjects.allComponentClasses[attributeSpecification.createComponentOfType]; if (!attributeClass) { @@ -3024,11 +3034,16 @@ export default class Core { let thisDependencies = {} - if (attributeFromPrimitive) { + if (attributeSpecification.createPrimitiveOfType) { thisDependencies.attributePrimitive = { dependencyType: "attributePrimitive", attributeName: attrName } + } else if (attributeSpecification.createTargetComponentNames) { + thisDependencies.attributeTargetComponentNames = { + dependencyType: "attributeTargetComponentNames", + attributeName: attrName + } } else { thisDependencies.attributeComponent = { dependencyType: "attributeComponent", @@ -3053,6 +3068,8 @@ export default class Core { attributeValue = dependencyValues.attributeComponent.stateValues[stateVariableForAttributeValue]; } else if (dependencyValues.attributePrimitive !== undefined && dependencyValues.attributePrimitive !== null) { attributeValue = dependencyValues.attributePrimitive; + } else if (dependencyValues.attributeTargetComponentNames !== undefined && dependencyValues.attributeTargetComponentNames !== null) { + attributeValue = dependencyValues.attributeTargetComponentNames; } else { // parentValue would be undefined if fallBackToParentStateVariable wasn't specified @@ -3097,6 +3114,10 @@ export default class Core { // can't invert if have primitive return { success: false } } + if (dependencyValues.attributeTargetComponentNames !== undefined && dependencyValues.attributeTargetComponentNames !== null) { + // can't invert if have target component names + return { success: false } + } let haveParentValue = dependencyValues.parentValue !== undefined && dependencyValues.parentValue !== null; diff --git a/src/Core/Dependencies.js b/src/Core/Dependencies.js index a83bebc5d7..fdd7a17a78 100644 --- a/src/Core/Dependencies.js +++ b/src/Core/Dependencies.js @@ -6358,7 +6358,7 @@ class PrimaryShadowDependency extends Dependency { if (!primaryShadowDependencies.includes(this)) { primaryShadowDependencies.push(this); } - + if (!component.primaryShadow) { return { success: true, @@ -6897,6 +6897,58 @@ class CountAmongSiblingsDependency extends Dependency { dependencyTypeArray.push(CountAmongSiblingsDependency); +class AttributeTargetComponentNamesDependency extends StateVariableDependency { + static dependencyType = "attributeTargetComponentNames"; + + setUpParameters() { + + this.attributeName = this.definition.attributeName; + + if (this.definition.parentName) { + this.componentName = this.definition.parentName; + this.specifiedComponentName = this.componentName; + } else { + this.componentName = this.upstreamComponentName; + } + + } + + async getValue() { + + let value = null; + let changes = {}; + + if (this.componentIdentitiesChanged) { + changes.componentIdentitiesChanged = true; + this.componentIdentitiesChanged = false; + } + + if (this.downstreamComponentNames.length === 1) { + let parent = this.dependencyHandler.components[this.componentName]; + + if (parent) { + value = parent.attributes[this.attributeName]; + if (value) { + value = value.targetComponentNames; + } else { + value = null; + } + } + + } + + // if (!this.doNotProxy && value !== null && typeof value === 'object') { + // value = new Proxy(value, readOnlyProxyHandler) + // } + + return { value, changes } + } + +} + +dependencyTypeArray.push(AttributeTargetComponentNamesDependency); + + class TargetComponentDependency extends Dependency { static dependencyType = "targetComponent"; @@ -6993,53 +7045,6 @@ class TargetComponentDependency extends Dependency { dependencyTypeArray.push(TargetComponentDependency); - - -class ExpandTargetNameDependency extends Dependency { - static dependencyType = "expandTargetName"; - - setUpParameters() { - - this.parentName = this.upstreamComponentName; - - this.target = this.definition.target; - - } - - async getValue() { - - let parent = this.dependencyHandler._components[this.parentName]; - let parentCreatesNewNamespace = parent.attributes.newNamespace?.primitive; - - let namespaceStack = this.parentName.split('/').map(x => ({ namespace: x })) - - if (!parentCreatesNewNamespace) { - namespaceStack = namespaceStack.slice(0, namespaceStack.length - 1) - } - - let targetComponentName; - - try { - targetComponentName = convertComponentTarget({ - target: this.target, - namespaceStack, - }) - } catch (e) { - targetComponentName = null; - } - - return { - value: targetComponentName, - changes: {} - } - } - - -} - -dependencyTypeArray.push(ExpandTargetNameDependency); - - class ValueDependency extends Dependency { static dependencyType = "value"; diff --git a/src/Core/components/CallAction.js b/src/Core/components/CallAction.js index 97726ed60f..61332e098d 100644 --- a/src/Core/components/CallAction.js +++ b/src/Core/components/CallAction.js @@ -51,11 +51,11 @@ export default class CallAction extends InlineComponent { } attributes.triggerWith = { - createPrimitiveOfType: "string" + createTargetComponentNames: true, } attributes.triggerWhenObjectsClicked = { - createPrimitiveOfType: "string" + createTargetComponentNames: true, } attributes.numbers = { @@ -162,11 +162,11 @@ export default class CallAction extends InlineComponent { stateVariableDefinitions.triggerWith = { returnDependencies: () => ({ triggerWith: { - dependencyType: "attributePrimitive", + dependencyType: "attributeTargetComponentNames", attributeName: "triggerWith" }, triggerWhenObjectsClicked: { - dependencyType: "attributePrimitive", + dependencyType: "attributeTargetComponentNames", attributeName: "triggerWhenObjectsClicked" }, triggerWhen: { @@ -185,13 +185,13 @@ export default class CallAction extends InlineComponent { let triggerWith = []; if (dependencyValues.triggerWith !== null) { - for (let target of dependencyValues.triggerWith.split(/\s+/).filter(s => s)) { - triggerWith.push({ target }) + for (let nameObj of dependencyValues.triggerWith) { + triggerWith.push({ target: nameObj.absoluteName }); } } if (dependencyValues.triggerWhenObjectsClicked !== null) { - for (let target of dependencyValues.triggerWhenObjectsClicked.split(/\s+/).filter(s => s)) { - triggerWith.push({ target, triggeringAction: "click" }) + for (let nameObj of dependencyValues.triggerWhenObjectsClicked) { + triggerWith.push({ target: nameObj.absoluteName, triggeringAction: "click" }) } } @@ -208,32 +208,19 @@ export default class CallAction extends InlineComponent { chainActionOnActionOfStateVariableTargets: { triggeredAction: "callAction" }, - stateVariablesDeterminingDependencies: ["triggerWith"], - returnDependencies({ stateValues }) { - let dependencies = { - triggerWith: { - dependencyType: "stateVariable", - variableName: "triggerWith" - } - }; - if (stateValues.triggerWith) { - for (let [ind, targetObj] of stateValues.triggerWith.entries()) { - - dependencies[`triggerWithTargetComponentName${ind}`] = { - dependencyType: "expandTargetName", - target: targetObj.target - } - } + returnDependencies: () => ({ + triggerWith: { + dependencyType: "stateVariable", + variableName: "triggerWith" } - return dependencies; - }, + }), definition({ dependencyValues }) { let triggerWithTargetIds = []; if (dependencyValues.triggerWith) { - for (let [ind, targetObj] of dependencyValues.triggerWith.entries()) { + for (let targetObj of dependencyValues.triggerWith) { - let id = dependencyValues[`triggerWithTargetComponentName${ind}`]; + let id = targetObj.target; if (targetObj.triggeringAction) { id += "|" + targetObj.triggeringAction; diff --git a/src/Core/components/CodeViewer.js b/src/Core/components/CodeViewer.js index a3e95803a5..02b3182db0 100644 --- a/src/Core/components/CodeViewer.js +++ b/src/Core/components/CodeViewer.js @@ -10,9 +10,7 @@ export default class CodeViewer extends BlockComponent { let attributes = super.createAttributesObject(); attributes.codeSource = { - createPrimitiveOfType: "string", - createStateVariable: "rawCodeSource", - defaultValue: null, + createTargetComponentNames: true, } attributes.width = { @@ -52,7 +50,7 @@ export default class CodeViewer extends BlockComponent { if (componentAttributes.codeSource) { renderDoenetML.attributes = { codeSource: { - primitive: componentAttributes.codeSource + targetComponentNames: componentAttributes.codeSource } } } @@ -178,21 +176,22 @@ export default class CodeViewer extends BlockComponent { } stateVariableDefinitions.codeSourceComponentName = { - stateVariablesDeterminingDependencies: ["rawCodeSource"], - returnDependencies({ stateValues }) { - if (stateValues.rawCodeSource) { - return { - codeSourceComponentName: { - dependencyType: "expandTargetName", - target: stateValues.rawCodeSource - } - } - } else { - return {} + returnDependencies: () => ({ + codeSource: { + dependencyType: "attributeTargetComponentNames", + attributeName: "codeSource" } - }, + }), definition({ dependencyValues }) { - return { setValue: { codeSourceComponentName: dependencyValues.codeSourceComponentName } } + let codeSourceComponentName; + + if (dependencyValues.codeSource?.length === 1) { + codeSourceComponentName = dependencyValues.codeSource[0].absoluteName; + } else { + codeSourceComponentName = null; + } + + return { setValue: { codeSourceComponentName } } } } diff --git a/src/Core/components/Constraints.js b/src/Core/components/Constraints.js index 281bce9f86..e6c010593b 100644 --- a/src/Core/components/Constraints.js +++ b/src/Core/components/Constraints.js @@ -10,9 +10,7 @@ export default class Constraints extends BaseComponent { let attributes = super.createAttributesObject(); attributes.baseOnGraph = { - createPrimitiveOfType: "string", - createStateVariable: "baseOnGraph", - defaultValue: null, + createTargetComponentNames: true, } return attributes @@ -92,22 +90,15 @@ export default class Constraints extends BaseComponent { } stateVariableDefinitions.graphComponentName = { - stateVariablesDeterminingDependencies: ["baseOnGraph"], - returnDependencies({ stateValues }) { - if (stateValues.baseOnGraph) { - return { - graphComponentName: { - dependencyType: "expandTargetName", - target: stateValues.baseOnGraph - } - } - } else { - return {} + returnDependencies: () => ({ + graphComponentName: { + dependencyType: "attributeTargetComponentNames", + attributeName: "baseOnGraph" } - }, + }), definition({ dependencyValues }) { - if (dependencyValues.graphComponentName) { - return { setValue: { graphComponentName: dependencyValues.graphComponentName } } + if (dependencyValues.graphComponentName?.length === 1) { + return { setValue: { graphComponentName: dependencyValues.graphComponentName[0].absoluteName } } } else { return { setValue: { graphComponentName: null } } } diff --git a/src/Core/components/Feedback.js b/src/Core/components/Feedback.js index 0d62c1f37a..a852583629 100644 --- a/src/Core/components/Feedback.js +++ b/src/Core/components/Feedback.js @@ -13,7 +13,7 @@ export default class Feedback extends BlockComponent { createComponentOfType: "boolean" } attributes.updateWith = { - createPrimitiveOfType: "string" + createTargetComponentNames: true, } return attributes; @@ -32,40 +32,19 @@ export default class Feedback extends BlockComponent { let stateVariableDefinitions = super.returnStateVariableDefinitions(); - stateVariableDefinitions.updateWith = { - returnDependencies: () => ({ - updateWithAttr: { - dependencyType: "attributePrimitive", - attributeName: "updateWith" - } - }), - definition({ dependencyValues }) { - return { - setValue: { updateWith: dependencyValues.updateWithAttr } - } - } - } - stateVariableDefinitions.updateWithComponentNames = { chainActionOnActionOfStateVariableTargets: { triggeredAction: "updateHide" }, - stateVariablesDeterminingDependencies: ["updateWith"], - returnDependencies({ stateValues }) { - if (stateValues.updateWith) { - return { - updateWithComponentName: { - dependencyType: "expandTargetName", - target: stateValues.updateWith - } - } - } else { - return {} + returnDependencies: () => ({ + updateWith: { + dependencyType: "attributeTargetComponentNames", + attributeName: "updateWith" } - }, + }), definition({ dependencyValues }) { - if (dependencyValues.updateWithComponentName) { - return { setValue: { updateWithComponentNames: [dependencyValues.updateWithComponentName] } } + if (dependencyValues.updateWith) { + return { setValue: { updateWithComponentNames: dependencyValues.updateWith.map(x => x.absoluteName) } } } else { return { setValue: { updateWithComponentNames: [] } } } @@ -105,27 +84,24 @@ export default class Feedback extends BlockComponent { forRenderer: true, defaultValue: true, hasEssential: true, - stateVariablesDeterminingDependencies: ["updateWith"], - returnDependencies({ stateValues }) { - if (stateValues.updateWith) { - return {}; - } else { - return { - condition: { - dependencyType: "attributeComponent", - attributeName: "condition", - variableNames: ["value"], - }, - showFeedback: { - dependencyType: "flag", - flagName: "showFeedback", - } - } + returnDependencies: () => ({ + updateWith: { + dependencyType: "attributeTargetComponentNames", + attributeName: "updateWith" + }, + condition: { + dependencyType: "attributeComponent", + attributeName: "condition", + variableNames: ["value"], + }, + showFeedback: { + dependencyType: "flag", + flagName: "showFeedback", } - }, + }), definition: function ({ dependencyValues }) { - if (!("condition" in dependencyValues)) { + if (dependencyValues.updateWith) { return { useEssentialOrDefaultValue: { hide: true } } @@ -145,7 +121,7 @@ export default class Feedback extends BlockComponent { return { setValue: { hide } } }, inverseDefinition({ desiredStateVariableValues, dependencyValues }) { - if ("condition" in dependencyValues) { + if (!dependencyValues.updateWith) { return { success: false } } else { return { diff --git a/src/Core/components/Label.js b/src/Core/components/Label.js index f8e4cba7ce..0283ee061d 100644 --- a/src/Core/components/Label.js +++ b/src/Core/components/Label.js @@ -17,10 +17,7 @@ export default class Label extends InlineComponent { let attributes = super.createAttributesObject(); attributes.forObject = { - createPrimitiveOfType: "string", - createStateVariable: "forObject", - defaultValue: null, - public: true, + createTargetComponentNames: true, } attributes.draggable = { @@ -367,15 +364,22 @@ export default class Label extends InlineComponent { } stateVariableDefinitions.forObjectComponentName = { - stateVariablesDeterminingDependencies: ["forObject"], - returnDependencies: ({ stateValues }) => ({ - forObjectComponentName: { - dependencyType: "expandTargetName", - target: stateValues.forObject + returnDependencies: () => ({ + forObject: { + dependencyType: "attributeTargetComponentNames", + attributeName: "forObject" } }), definition({ dependencyValues }) { - return { setValue: { forObjectComponentName: dependencyValues.forObjectComponentName } } + let forObjectComponentName; + + if (dependencyValues.forObject?.length === 1) { + forObjectComponentName = dependencyValues.forObject[0].absoluteName; + } else { + forObjectComponentName = null; + } + + return { setValue: { forObjectComponentName } } } } diff --git a/src/Core/components/Paginator.js b/src/Core/components/Paginator.js index 899fafdb3e..b042d242e4 100644 --- a/src/Core/components/Paginator.js +++ b/src/Core/components/Paginator.js @@ -26,7 +26,7 @@ export class Paginator extends BlockComponent { } - + static returnStateVariableDefinitions() { let stateVariableDefinitions = super.returnStateVariableDefinitions(); @@ -236,9 +236,7 @@ export class PaginatorControls extends BlockComponent { public: true, } attributes.paginator = { - createPrimitiveOfType: "string", - createStateVariable: "paginator", - defaultValue: null, + createTargetComponentNames: true, } return attributes; @@ -252,21 +250,22 @@ export class PaginatorControls extends BlockComponent { stateVariableDefinitions.paginatorComponentName = { - stateVariablesDeterminingDependencies: ["paginator"], - returnDependencies({ stateValues }) { - if (stateValues.paginator) { - return { - paginatorComponentName: { - dependencyType: "expandTargetName", - target: stateValues.paginator - } - } - } else { - return {} + returnDependencies: () => ({ + paginator: { + dependencyType: "attributeTargetComponentNames", + attributeName: "paginator" } - }, + }), definition({ dependencyValues }) { - return { setValue: { paginatorComponentName: dependencyValues.paginatorComponentName } } + let paginatorComponentName; + + if (dependencyValues.paginator?.length === 1) { + paginatorComponentName = dependencyValues.paginator[0].absoluteName; + } else { + paginatorComponentName = null; + } + + return { setValue: { paginatorComponentName } } } } diff --git a/src/Core/components/RenderDoenetML.js b/src/Core/components/RenderDoenetML.js index 5eb02c45f7..5043d1df2a 100644 --- a/src/Core/components/RenderDoenetML.js +++ b/src/Core/components/RenderDoenetML.js @@ -20,9 +20,7 @@ export default class RenderDoenetML extends CompositeComponent { } attributes.codeSource = { - createPrimitiveOfType: "string", - createStateVariable: "rawCodeSource", - defaultValue: null, + createTargetComponentNames: true, } @@ -38,21 +36,22 @@ export default class RenderDoenetML extends CompositeComponent { let stateVariableDefinitions = super.returnStateVariableDefinitions(); stateVariableDefinitions.codeSourceComponentName = { - stateVariablesDeterminingDependencies: ["rawCodeSource"], - returnDependencies({ stateValues }) { - if (stateValues.rawCodeSource) { - return { - codeSourceComponentName: { - dependencyType: "expandTargetName", - target: stateValues.rawCodeSource - } - } - } else { - return {} + returnDependencies: () => ({ + codeSource: { + dependencyType: "attributeTargetComponentNames", + attributeName: "codeSource" } - }, + }), definition({ dependencyValues }) { - return { setValue: { codeSourceComponentName: dependencyValues.codeSourceComponentName } } + let codeSourceComponentName; + + if (dependencyValues.codeSource?.length === 1) { + codeSourceComponentName = dependencyValues.codeSource[0].absoluteName; + } else { + codeSourceComponentName = null; + } + + return { setValue: { codeSourceComponentName } } } } diff --git a/src/Core/components/SummaryStatistics.js b/src/Core/components/SummaryStatistics.js index d8a6b33443..33a180b87e 100644 --- a/src/Core/components/SummaryStatistics.js +++ b/src/Core/components/SummaryStatistics.js @@ -9,9 +9,7 @@ export default class SummaryStatistics extends BlockComponent { let attributes = super.createAttributesObject(); attributes.source = { - createPrimitiveOfType: "string", - createStateVariable: "source", - defaultValue: null, + createTargetComponentNames: true, } attributes.column = { @@ -155,17 +153,19 @@ export default class SummaryStatistics extends BlockComponent { }; stateVariableDefinitions.sourceName = { - stateVariablesDeterminingDependencies: ["source"], - returnDependencies: ({stateValues}) => ({ - sourceName: { - dependencyType: "expandTargetName", - target: stateValues.source + returnDependencies: () => ({ + source: { + dependencyType: "attributeTargetComponentNames", + attributeName: "source" } }), definition({ dependencyValues }) { - let sourceName = null; - if (dependencyValues.sourceName) { - sourceName = dependencyValues.sourceName + let sourceName; + + if (dependencyValues.source?.length === 1) { + sourceName = dependencyValues.source[0].absoluteName; + } else { + sourceName = null; } return { setValue: { sourceName } } } @@ -182,7 +182,7 @@ export default class SummaryStatistics extends BlockComponent { }, forRenderer: true, }], - returnDependencies({stateValues}) { + returnDependencies({ stateValues }) { return { dataFrame: { dependencyType: "stateVariable", diff --git a/src/Core/components/TriggerSet.js b/src/Core/components/TriggerSet.js index 7106988d57..c31c9671fc 100644 --- a/src/Core/components/TriggerSet.js +++ b/src/Core/components/TriggerSet.js @@ -24,11 +24,11 @@ export default class triggerSet extends InlineComponent { } attributes.triggerWith = { - createPrimitiveOfType: "string" + createTargetComponentNames: true, } attributes.triggerWhenObjectsClicked = { - createPrimitiveOfType: "string" + createTargetComponentNames: true, } return attributes; @@ -75,11 +75,11 @@ export default class triggerSet extends InlineComponent { stateVariableDefinitions.triggerWith = { returnDependencies: () => ({ triggerWith: { - dependencyType: "attributePrimitive", + dependencyType: "attributeTargetComponentNames", attributeName: "triggerWith" }, triggerWhenObjectsClicked: { - dependencyType: "attributePrimitive", + dependencyType: "attributeTargetComponentNames", attributeName: "triggerWhenObjectsClicked" }, triggerWhen: { @@ -93,13 +93,13 @@ export default class triggerSet extends InlineComponent { } else { let triggerWith = []; if (dependencyValues.triggerWith !== null) { - for (let target of dependencyValues.triggerWith.split(/\s+/).filter(s => s)) { - triggerWith.push({ target }) + for (let nameObj of dependencyValues.triggerWith) { + triggerWith.push({ target: nameObj.absoluteName }); } } if (dependencyValues.triggerWhenObjectsClicked !== null) { - for (let target of dependencyValues.triggerWhenObjectsClicked.split(/\s+/).filter(s => s)) { - triggerWith.push({ target, triggeringAction: "click" }) + for (let nameObj of dependencyValues.triggerWhenObjectsClicked) { + triggerWith.push({ target: nameObj.absoluteName, triggeringAction: "click" }) } } @@ -116,32 +116,19 @@ export default class triggerSet extends InlineComponent { chainActionOnActionOfStateVariableTargets: { triggeredAction: "triggerActions" }, - stateVariablesDeterminingDependencies: ["triggerWith"], - returnDependencies({ stateValues }) { - let dependencies = { - triggerWith: { - dependencyType: "stateVariable", - variableName: "triggerWith" - } - }; - if (stateValues.triggerWith) { - for (let [ind, targetObj] of stateValues.triggerWith.entries()) { - - dependencies[`triggerWithTargetComponentName${ind}`] = { - dependencyType: "expandTargetName", - target: targetObj.target - } - } + returnDependencies: () => ({ + triggerWith: { + dependencyType: "stateVariable", + variableName: "triggerWith" } - return dependencies; - }, + }), definition({ dependencyValues }) { let triggerWithTargetIds = []; if (dependencyValues.triggerWith) { - for (let [ind, targetObj] of dependencyValues.triggerWith.entries()) { + for (let targetObj of dependencyValues.triggerWith) { - let id = dependencyValues[`triggerWithTargetComponentName${ind}`]; + let id = targetObj.target; if (targetObj.triggeringAction) { id += "|" + targetObj.triggeringAction; diff --git a/src/Core/components/UpdateValue.js b/src/Core/components/UpdateValue.js index 597f0e43da..b83ab5bf1f 100644 --- a/src/Core/components/UpdateValue.js +++ b/src/Core/components/UpdateValue.js @@ -57,11 +57,11 @@ export default class UpdateValue extends InlineComponent { } attributes.triggerWith = { - createPrimitiveOfType: "string" + createTargetComponentNames: "string" } attributes.triggerWhenObjectsClicked = { - createPrimitiveOfType: "string" + createTargetComponentNames: "string" } // for newValue with type==="math" @@ -318,11 +318,11 @@ export default class UpdateValue extends InlineComponent { stateVariableDefinitions.triggerWith = { returnDependencies: () => ({ triggerWith: { - dependencyType: "attributePrimitive", + dependencyType: "attributeTargetComponentNames", attributeName: "triggerWith" }, triggerWhenObjectsClicked: { - dependencyType: "attributePrimitive", + dependencyType: "attributeTargetComponentNames", attributeName: "triggerWhenObjectsClicked" }, triggerWhen: { @@ -341,13 +341,13 @@ export default class UpdateValue extends InlineComponent { let triggerWith = []; if (dependencyValues.triggerWith !== null) { - for (let target of dependencyValues.triggerWith.split(/\s+/).filter(s => s)) { - triggerWith.push({ target }) + for (let nameObj of dependencyValues.triggerWith) { + triggerWith.push({ target: nameObj.absoluteName }); } } if (dependencyValues.triggerWhenObjectsClicked !== null) { - for (let target of dependencyValues.triggerWhenObjectsClicked.split(/\s+/).filter(s => s)) { - triggerWith.push({ target, triggeringAction: "click" }) + for (let nameObj of dependencyValues.triggerWhenObjectsClicked) { + triggerWith.push({ target: nameObj.absoluteName, triggeringAction: "click" }) } } @@ -364,32 +364,19 @@ export default class UpdateValue extends InlineComponent { chainActionOnActionOfStateVariableTargets: { triggeredAction: "updateValue" }, - stateVariablesDeterminingDependencies: ["triggerWith"], - returnDependencies({ stateValues }) { - let dependencies = { - triggerWith: { - dependencyType: "stateVariable", - variableName: "triggerWith" - } - }; - if (stateValues.triggerWith) { - for (let [ind, targetObj] of stateValues.triggerWith.entries()) { - - dependencies[`triggerWithTargetComponentName${ind}`] = { - dependencyType: "expandTargetName", - target: targetObj.target - } - } + returnDependencies: () => ({ + triggerWith: { + dependencyType: "stateVariable", + variableName: "triggerWith" } - return dependencies; - }, + }), definition({ dependencyValues }) { let triggerWithTargetIds = []; if (dependencyValues.triggerWith) { - for (let [ind, targetObj] of dependencyValues.triggerWith.entries()) { + for (let targetObj of dependencyValues.triggerWith) { - let id = dependencyValues[`triggerWithTargetComponentName${ind}`]; + let id = targetObj.target; if (targetObj.triggeringAction) { id += "|" + targetObj.triggeringAction; diff --git a/src/Core/utils/serializedStateProcessing.js b/src/Core/utils/serializedStateProcessing.js index b6e7222af9..c4e7b20300 100644 --- a/src/Core/utils/serializedStateProcessing.js +++ b/src/Core/utils/serializedStateProcessing.js @@ -151,7 +151,7 @@ export async function expandDoenetMLsToFullSerializedComponents({ }; originalCopyWithUri.children = [extContent, ...originalCopyWithUri.children]; - + } } } @@ -926,7 +926,7 @@ export function componentFromAttribute({ attrObj, value, originalComponentProps, } else if (attrObj.createPrimitiveOfType === "stringArray") { newPrimitive = value.rawString.trim().split(/\s+/); } else if (attrObj.createPrimitiveOfType === "numberArray") { - newPrimitive = value.rawString.split(/\s+/).map(Number); + newPrimitive = value.rawString.trim().split(/\s+/).map(Number); } else { // else assume string newPrimitive = value.rawString; @@ -936,6 +936,24 @@ export function componentFromAttribute({ attrObj, value, originalComponentProps, newPrimitive = attrObj.validationFunction(newPrimitive); } return { primitive: newPrimitive }; + } else if (attrObj && attrObj.createTargetComponentNames) { + let newTargets = value.rawString.trim().split(/\s+/).map(str => { + if (str[0] === "$" && str[1] !== "$") { + // remove unnecessary macro notation + str = str.slice(1); + + if (str[0] === "(" && str[str.length - 1] === ")") { + // remove unnecessary parens from macro + // (don't both checking that parens match, as no valid result with multiple parens) + str = str.slice(1, str.length - 1); + } + + } + // absolute name will be added when namespace is known + return { relativeName: str } + }) + + return { targetComponentNames: newTargets }; } else { if (!value.childrenForComponent) { value.childrenForComponent = [value.rawString] @@ -2089,7 +2107,7 @@ export function createComponentNames({ serializedComponents, namespaceStack = [] parentDoenetAttributes = {}, parentName, useOriginalNames = false, - doenetAttributesByTargetComponentName, + attributesByTargetComponentName, indOffset = 0, createNameContext = "", initWithoutShadowingComposite = false, @@ -2375,16 +2393,16 @@ export function createComponentNames({ serializedComponents, namespaceStack = [] oldNamespace = serializedComponent.originalName + '/'; } - let newAssignNames = createNewAssignNamesAndRenameMatchingTNames({ + let newAssignNames = createNewAssignNamesAndrenameMatchingTargetNames({ originalAssignNames, longNameIdBase, - namespace, oldNamespace, doenetAttributesByTargetComponentName + namespace, oldNamespace, attributesByTargetComponentName }); assignNames = serializedComponent.doenetAttributes.assignNames = newAssignNames; } - renameMatchingTNames(serializedComponent, doenetAttributesByTargetComponentName); + renameMatchingTargetNames(serializedComponent, attributesByTargetComponentName); if (target) { if (!componentClass.acceptTarget) { @@ -2399,14 +2417,27 @@ export function createComponentNames({ serializedComponents, namespaceStack = [] doenetAttributes.target = target; doenetAttributes.targetComponentName = convertComponentTarget({ - target, - oldTargetComponentName: doenetAttributes.targetComponentName, + relativeName: target, + oldAbsoluteName: doenetAttributes.targetComponentName, namespaceStack, acceptDoubleUnderscore: doenetAttributes.createdFromSugar || doenetAttributes.allowDoubleUnderscoreTarget }); } + for (let attrName in attributes) { + let attr = attributes[attrName]; + if (attr.targetComponentNames) { + for (let nameObj of attr.targetComponentNames) { + nameObj.absoluteName = convertComponentTarget({ + relativeName: nameObj.relativeName, + oldAbsoluteName: nameObj.absoluteName, + namespaceStack, + acceptDoubleUnderscore: doenetAttributes.createdFromSugar || doenetAttributes.allowDoubleUnderscoreTarget + }) + } + } + } if (serializedComponent.children) { @@ -2436,7 +2467,7 @@ export function createComponentNames({ serializedComponents, namespaceStack = [] parentDoenetAttributes: doenetAttributes, parentName: componentName, useOriginalNames, - doenetAttributesByTargetComponentName, + attributesByTargetComponentName, }); currentNamespace.namesUsed = originalNamesUsed; @@ -2451,7 +2482,7 @@ export function createComponentNames({ serializedComponents, namespaceStack = [] parentDoenetAttributes: doenetAttributes, parentName: componentName, useOriginalNames, - doenetAttributesByTargetComponentName, + attributesByTargetComponentName, }); } else { @@ -2475,7 +2506,7 @@ export function createComponentNames({ serializedComponents, namespaceStack = [] children = children.slice(1) - let separateNewNamespaceInfo = { namespace: prescribedName, componentCounts: {}, namesUsed:{} }; + let separateNewNamespaceInfo = { namespace: prescribedName, componentCounts: {}, namesUsed: {} }; namespaceStack.push(separateNewNamespaceInfo); createComponentNames({ @@ -2485,7 +2516,7 @@ export function createComponentNames({ serializedComponents, namespaceStack = [] parentDoenetAttributes: doenetAttributes, parentName: componentName, useOriginalNames, - doenetAttributesByTargetComponentName, + attributesByTargetComponentName, }); namespaceStack.pop(); @@ -2534,7 +2565,7 @@ export function createComponentNames({ serializedComponents, namespaceStack = [] parentDoenetAttributes: doenetAttributes, parentName: componentName, useOriginalNames, - doenetAttributesByTargetComponentName, + attributesByTargetComponentName, }); if (addingNewNamespace) { @@ -2552,7 +2583,7 @@ export function createComponentNames({ serializedComponents, namespaceStack = [] parentDoenetAttributes: doenetAttributes, parentName: componentName, useOriginalNames, - doenetAttributesByTargetComponentName, + attributesByTargetComponentName, }); namespaceStack.pop(); } @@ -2589,7 +2620,7 @@ export function createComponentNames({ serializedComponents, namespaceStack = [] parentDoenetAttributes: doenetAttributes, parentName: componentName, useOriginalNames, - doenetAttributesByTargetComponentName, + attributesByTargetComponentName, createNameContext: attrName }); @@ -2605,7 +2636,7 @@ export function createComponentNames({ serializedComponents, namespaceStack = [] parentDoenetAttributes: doenetAttributes, parentName: componentName, useOriginalNames, - doenetAttributesByTargetComponentName, + attributesByTargetComponentName, createNameContext: attrName }); } @@ -2621,9 +2652,9 @@ export function createComponentNames({ serializedComponents, namespaceStack = [] } -function createNewAssignNamesAndRenameMatchingTNames({ +function createNewAssignNamesAndrenameMatchingTargetNames({ originalAssignNames, longNameIdBase, - namespace, oldNamespace, doenetAttributesByTargetComponentName + namespace, oldNamespace, attributesByTargetComponentName }) { let assignNames = []; @@ -2632,10 +2663,10 @@ function createNewAssignNamesAndRenameMatchingTNames({ if (Array.isArray(originalName)) { // recurse to next level - let assignNamesSub = createNewAssignNamesAndRenameMatchingTNames({ + let assignNamesSub = createNewAssignNamesAndrenameMatchingTargetNames({ originalAssignNames: originalName, longNameIdBase: longNameIdBase + ind + "_", - namespace, oldNamespace, doenetAttributesByTargetComponentName + namespace, oldNamespace, attributesByTargetComponentName }) assignNames.push(assignNamesSub); } else { @@ -2649,7 +2680,7 @@ function createNewAssignNamesAndRenameMatchingTNames({ originalName: oldNamespace + originalName }; - renameMatchingTNames(infoForRenaming, doenetAttributesByTargetComponentName, true); + renameMatchingTargetNames(infoForRenaming, attributesByTargetComponentName, true); } } @@ -2659,54 +2690,54 @@ function createNewAssignNamesAndRenameMatchingTNames({ } export function convertComponentTarget({ - target, - oldTargetComponentName, + relativeName, + oldAbsoluteName, namespaceStack, acceptDoubleUnderscore, }) { - if (!oldTargetComponentName && /__/.test(target) && !acceptDoubleUnderscore) { - throw Error("Invalid reference target: " + target); + if (!oldAbsoluteName && /__/.test(relativeName) && !acceptDoubleUnderscore) { + throw Error("Invalid reference target: " + relativeName); } - let targetComponentName; + let absoluteName; - // console.log(`target: ${target}`) + // console.log(`relativeName: ${relativeName}`) // console.log(JSON.parse(JSON.stringify(namespaceStack))) - if (target.substring(0, 1) === '/') { + if (relativeName.substring(0, 1) === '/') { // if starts with /, then don't add anything to path - targetComponentName = target; + absoluteName = relativeName; } else { - // calculate full target from target + // calculate full target from relativeName // putting it into the context of the current namespace let lastLevel = namespaceStack.length - 1; - while (target.substring(0, 3) === '../') { + while (relativeName.substring(0, 3) === '../') { // take off one level for every ../ - target = target.substring(3); + relativeName = relativeName.substring(3); lastLevel--; } if (lastLevel < 0) { - // the target cannot possibly be valid + // the relativeName cannot possibly be valid // if there were more ../s than namespace levels lastLevel = 0; } - targetComponentName = ''; + absoluteName = ''; for (let l = 0; l <= lastLevel; l++) { - targetComponentName += namespaceStack[l].namespace + '/'; + absoluteName += namespaceStack[l].namespace + '/'; } - targetComponentName += target; + absoluteName += relativeName; } - return targetComponentName; + return absoluteName; } @@ -2764,7 +2795,7 @@ export function processAssignNames({ // or directly from a serialized state that was already given names moveComponentNamesToOriginalNames(serializedComponents); - let doenetAttributesByTargetComponentName = {}; + let attributesByTargetComponentName = {}; let originalNamespace = null; @@ -2785,7 +2816,7 @@ export function processAssignNames({ setTargetsOutsideNamespaceToAbsoluteAndRecordAllTargetComponentNames({ namespace: originalNamespace, components: [component], - doenetAttributesByTargetComponentName + attributesByTargetComponentName }); } } @@ -2809,7 +2840,7 @@ export function processAssignNames({ setTargetsOutsideNamespaceToAbsoluteAndRecordAllTargetComponentNames({ namespace: originalNamespace, components: [component], - doenetAttributesByTargetComponentName + attributesByTargetComponentName }); } @@ -2842,7 +2873,7 @@ export function processAssignNames({ } if (!originalNamesAreConsistent) { - // doenetAttributesByTargetComponentName = {}; + // attributesByTargetComponentName = {}; originalNamespace = null; // need to use a component for original name, as parentName is the new name @@ -2937,7 +2968,7 @@ export function processAssignNames({ ind: indForNames, component, parentCreatesNewNamespace, componentInfoObjects, - doenetAttributesByTargetComponentName, + attributesByTargetComponentName, originalNamesAreConsistent, shadowingComposite, }); @@ -2957,7 +2988,7 @@ function createComponentNamesFromParentName({ parentName, component, ind, parentCreatesNewNamespace, componentInfoObjects, - doenetAttributesByTargetComponentName, + attributesByTargetComponentName, originalNamesAreConsistent, shadowingComposite, }) { @@ -3034,7 +3065,7 @@ function createComponentNamesFromParentName({ componentInfoObjects, parentName, useOriginalNames, - doenetAttributesByTargetComponentName, + attributesByTargetComponentName, indOffset: ind, initWithoutShadowingComposite: !shadowingComposite, }); @@ -3044,7 +3075,7 @@ function createComponentNamesFromParentName({ } -function setTargetsOutsideNamespaceToAbsoluteAndRecordAllTargetComponentNames({ namespace, components, doenetAttributesByTargetComponentName }) { +function setTargetsOutsideNamespaceToAbsoluteAndRecordAllTargetComponentNames({ namespace, components, attributesByTargetComponentName }) { let namespaceLength = namespace.length; for (let component of components) { @@ -3058,53 +3089,84 @@ function setTargetsOutsideNamespaceToAbsoluteAndRecordAllTargetComponentNames({ if (targetComponentName.substring(0, namespaceLength) !== namespace) { component.doenetAttributes.target = targetComponentName; } - if (!doenetAttributesByTargetComponentName[targetComponentName]) { - doenetAttributesByTargetComponentName[targetComponentName] = []; + if (!attributesByTargetComponentName[targetComponentName]) { + attributesByTargetComponentName[targetComponentName] = []; + } + attributesByTargetComponentName[targetComponentName].push(component.doenetAttributes); + } + } + + for (let attrName in component.attributes) { + let attr = component.attributes[attrName]; + if (attr.targetComponentNames) { + for (let nameObj of attr.targetComponentNames) { + let absoluteName = nameObj.absoluteName; + if (absoluteName !== undefined) { + if (absoluteName.substring(0, namespaceLength) !== namespace) { + nameObj.relativeName = absoluteName; + } + if (!attributesByTargetComponentName[absoluteName]) { + attributesByTargetComponentName[absoluteName] = []; + } + attributesByTargetComponentName[absoluteName].push(nameObj); + } } - doenetAttributesByTargetComponentName[targetComponentName].push(component.doenetAttributes); } } if (component.children) { - setTargetsOutsideNamespaceToAbsoluteAndRecordAllTargetComponentNames({ namespace, components: component.children, doenetAttributesByTargetComponentName }) + setTargetsOutsideNamespaceToAbsoluteAndRecordAllTargetComponentNames({ namespace, components: component.children, attributesByTargetComponentName }) } if (component.attributes) { for (let attrName in component.attributes) { let attribute = component.attributes[attrName]; if (attribute.component) { - setTargetsOutsideNamespaceToAbsoluteAndRecordAllTargetComponentNames({ namespace, components: [attribute.component], doenetAttributesByTargetComponentName }) + setTargetsOutsideNamespaceToAbsoluteAndRecordAllTargetComponentNames({ namespace, components: [attribute.component], attributesByTargetComponentName }) } else if (attribute.childrenForComponent) { - setTargetsOutsideNamespaceToAbsoluteAndRecordAllTargetComponentNames({ namespace, components: attribute.childrenForComponent, doenetAttributesByTargetComponentName }) + setTargetsOutsideNamespaceToAbsoluteAndRecordAllTargetComponentNames({ namespace, components: attribute.childrenForComponent, attributesByTargetComponentName }) } } } } } -function renameMatchingTNames(component, doenetAttributesByTargetComponentName, renameMatchingNamespaces = false) { +function renameMatchingTargetNames(component, attributesByTargetComponentName, renameMatchingNamespaces = false) { + if (component.originalName && - doenetAttributesByTargetComponentName + attributesByTargetComponentName && component.componentName !== component.originalName) { // we have a component who has been named and there are other components // whose targetComponentName refers to this component // Modify the target and targetComponentName of the other components to refer to the new name // (Must modify targetComponentName as we don't know if this component has been processed yet) - if (doenetAttributesByTargetComponentName[component.originalName]) { - for (let dAttributes of doenetAttributesByTargetComponentName[component.originalName]) { - dAttributes.target = component.componentName; - dAttributes.targetComponentName = component.componentName; + if (attributesByTargetComponentName[component.originalName]) { + for (let attrObj of attributesByTargetComponentName[component.originalName]) { + if (attrObj.relativeName) { + attrObj.relativeName = component.componentName; + attrObj.absoluteName = component.componentName + } else { + // must be doenetAttributes + attrObj.target = component.componentName; + attrObj.targetComponentName = component.componentName; + } } } if (renameMatchingNamespaces) { let originalNamespace = component.originalName + "/"; let nSpaceLen = originalNamespace.length; - for (let originalTargetComponentName in doenetAttributesByTargetComponentName) { + for (let originalTargetComponentName in attributesByTargetComponentName) { if (originalTargetComponentName.substring(0, nSpaceLen) === originalNamespace) { let originalEnding = originalTargetComponentName.substring(nSpaceLen); - for (let dAttributes of doenetAttributesByTargetComponentName[originalTargetComponentName]) { - dAttributes.target = component.componentName + "/" + originalEnding; - dAttributes.targetComponentName = component.componentName + "/" + originalEnding; + for (let attrObj of attributesByTargetComponentName[originalTargetComponentName]) { + if (attrObj.relativeName) { + attrObj.relativeName = component.componentName + "/" + originalEnding; + attrObj.absoluteName = component.componentName + "/" + originalEnding + } else { + // must be doenetAttributes + attrObj.target = component.componentName + "/" + originalEnding; + attrObj.targetComponentName = component.componentName + "/" + originalEnding; + } } } } From fa02d92a3fb29644cab4de8848b8dc21cb9fd110 Mon Sep 17 00:00:00 2001 From: Duane Nykamp Date: Sat, 11 Feb 2023 20:23:31 -0600 Subject: [PATCH 03/13] triggerWhenMouseDownOnObjects, down action for point --- src/Core/components/CallAction.js | 13 +++++++++++++ src/Core/components/Point.js | 13 +++++++++++++ src/Core/components/TriggerSet.js | 13 +++++++++++++ src/Core/components/UpdateValue.js | 13 +++++++++++++ src/Viewer/renderers/point.jsx | 10 ++++++---- 5 files changed, 58 insertions(+), 4 deletions(-) diff --git a/src/Core/components/CallAction.js b/src/Core/components/CallAction.js index 61332e098d..dbf250f90e 100644 --- a/src/Core/components/CallAction.js +++ b/src/Core/components/CallAction.js @@ -58,6 +58,10 @@ export default class CallAction extends InlineComponent { createTargetComponentNames: true, } + attributes.triggerWhenMouseDownOnObjects = { + createTargetComponentNames: "string" + } + attributes.numbers = { createComponentOfType: "numberList", }; @@ -169,6 +173,10 @@ export default class CallAction extends InlineComponent { dependencyType: "attributeTargetComponentNames", attributeName: "triggerWhenObjectsClicked" }, + triggerWhenMouseDownOnObjects: { + dependencyType: "attributeTargetComponentNames", + attributeName: "triggerWhenMouseDownOnObjects" + }, triggerWhen: { dependencyType: "attributeComponent", attributeName: "triggerWhen" @@ -194,6 +202,11 @@ export default class CallAction extends InlineComponent { triggerWith.push({ target: nameObj.absoluteName, triggeringAction: "click" }) } } + if (dependencyValues.triggerWhenMouseDownOnObjects !== null) { + for (let nameObj of dependencyValues.triggerWhenMouseDownOnObjects) { + triggerWith.push({ target: nameObj.absoluteName, triggeringAction: "down" }) + } + } if (triggerWith.length === 0) { triggerWith = null; diff --git a/src/Core/components/Point.js b/src/Core/components/Point.js index 5357703ffc..69ea4475e7 100644 --- a/src/Core/components/Point.js +++ b/src/Core/components/Point.js @@ -1235,10 +1235,23 @@ export default class Point extends GraphicalComponent { } + async mouseDownOnPoint({ actionId }) { + + await this.coreFunctions.triggerChainedActions({ + triggeringAction: "down", + componentName: this.componentName, + }) + + this.coreFunctions.resolveAction({ actionId }); + + } + + actions = { movePoint: this.movePoint.bind(this), switchPoint: this.switchPoint.bind(this), pointClicked: this.pointClicked.bind(this), + mouseDownOnPoint: this.mouseDownOnPoint.bind(this), }; } diff --git a/src/Core/components/TriggerSet.js b/src/Core/components/TriggerSet.js index c31c9671fc..54b7247db0 100644 --- a/src/Core/components/TriggerSet.js +++ b/src/Core/components/TriggerSet.js @@ -31,6 +31,10 @@ export default class triggerSet extends InlineComponent { createTargetComponentNames: true, } + attributes.triggerWhenMouseDownOnObjects = { + createTargetComponentNames: "string" + } + return attributes; } @@ -82,6 +86,10 @@ export default class triggerSet extends InlineComponent { dependencyType: "attributeTargetComponentNames", attributeName: "triggerWhenObjectsClicked" }, + triggerWhenMouseDownOnObjects: { + dependencyType: "attributeTargetComponentNames", + attributeName: "triggerWhenMouseDownOnObjects" + }, triggerWhen: { dependencyType: "attributeComponent", attributeName: "triggerWhen" @@ -102,6 +110,11 @@ export default class triggerSet extends InlineComponent { triggerWith.push({ target: nameObj.absoluteName, triggeringAction: "click" }) } } + if (dependencyValues.triggerWhenMouseDownOnObjects !== null) { + for (let nameObj of dependencyValues.triggerWhenMouseDownOnObjects) { + triggerWith.push({ target: nameObj.absoluteName, triggeringAction: "down" }) + } + } if (triggerWith.length === 0) { triggerWith = null; diff --git a/src/Core/components/UpdateValue.js b/src/Core/components/UpdateValue.js index b83ab5bf1f..490115e6b9 100644 --- a/src/Core/components/UpdateValue.js +++ b/src/Core/components/UpdateValue.js @@ -64,6 +64,10 @@ export default class UpdateValue extends InlineComponent { createTargetComponentNames: "string" } + attributes.triggerWhenMouseDownOnObjects = { + createTargetComponentNames: "string" + } + // for newValue with type==="math" // let simplify="" or simplify="true" be full simplify attributes.simplify = { @@ -325,6 +329,10 @@ export default class UpdateValue extends InlineComponent { dependencyType: "attributeTargetComponentNames", attributeName: "triggerWhenObjectsClicked" }, + triggerWhenMouseDownOnObjects: { + dependencyType: "attributeTargetComponentNames", + attributeName: "triggerWhenMouseDownOnObjects" + }, triggerWhen: { dependencyType: "attributeComponent", attributeName: "triggerWhen" @@ -350,6 +358,11 @@ export default class UpdateValue extends InlineComponent { triggerWith.push({ target: nameObj.absoluteName, triggeringAction: "click" }) } } + if (dependencyValues.triggerWhenMouseDownOnObjects !== null) { + for (let nameObj of dependencyValues.triggerWhenMouseDownOnObjects) { + triggerWith.push({ target: nameObj.absoluteName, triggeringAction: "down" }) + } + } if (triggerWith.length === 0) { triggerWith = null; diff --git a/src/Viewer/renderers/point.jsx b/src/Viewer/renderers/point.jsx index 7b6613d646..0f69c5ae8b 100644 --- a/src/Viewer/renderers/point.jsx +++ b/src/Viewer/renderers/point.jsx @@ -170,7 +170,9 @@ export default React.memo(function Point(props) { dragged.current = false; shadowPointJXG.current.visProp.fillopacity = pointJXG.current.visProp.fillopacity; shadowPointJXG.current.visProp.strokeopacity = pointJXG.current.visProp.strokeopacity; - + callAction({ + action: actions.mouseDownOnPoint + }); }); newShadowPointJXG.on('up', function (e) { @@ -262,7 +264,7 @@ export default React.memo(function Point(props) { //if values update let fillColor = useOpenSymbol ? "var(--canvas)" : SVs.selectedStyle.markerColor; let strokeColor = useOpenSymbol ? SVs.selectedStyle.markerColor : "none"; - + if (pointJXG.current.visProp.fillcolor !== fillColor) { pointJXG.current.visProp.fillcolor = fillColor; } @@ -447,8 +449,8 @@ function normalizeSize(size, style) { } else if (style === "plus") { return size * 1.2; } else if (style === "square") { - return size * 1.1; - } else if (style.substring(0,8) === "triangle") { + return size * 1.1; + } else if (style.substring(0, 8) === "triangle") { return size * 1.5; } else return size; } From 83e65e8798aff814f55e597aba29552c9a79699b Mon Sep 17 00:00:00 2001 From: Duane Nykamp Date: Sat, 11 Feb 2023 21:41:04 -0600 Subject: [PATCH 04/13] mouse down action on remaining graphical components --- .../e2e/DoenetML/tagSpecific/callaction.cy.js | 140 ++++++++++++++++++ .../e2e/DoenetML/tagSpecific/triggerset.cy.js | 104 +++++++++++++ .../DoenetML/tagSpecific/updatevalue.cy.js | 80 ++++++++++ src/Core/components/Circle.js | 12 ++ src/Core/components/Curve.js | 14 +- src/Core/components/Line.js | 14 +- src/Core/components/LineSegment.js | 12 ++ src/Core/components/Polygon.js | 7 +- src/Core/components/Polyline.js | 14 +- src/Core/components/Ray.js | 12 ++ src/Core/components/Vector.js | 12 ++ src/Viewer/renderers/circle.jsx | 3 + src/Viewer/renderers/curve.jsx | 14 +- src/Viewer/renderers/line.jsx | 3 + src/Viewer/renderers/lineSegment.jsx | 12 ++ src/Viewer/renderers/polygon.jsx | 9 ++ src/Viewer/renderers/polyline.jsx | 9 ++ src/Viewer/renderers/ray.jsx | 3 + src/Viewer/renderers/vector.jsx | 9 ++ 19 files changed, 478 insertions(+), 5 deletions(-) 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) { From 03a9832aa9165c3921cb2502fcb66fe284efc417 Mon Sep 17 00:00:00 2001 From: Duane Nykamp Date: Wed, 15 Feb 2023 19:21:05 -0600 Subject: [PATCH 05/13] rename to triggerWhenObjectsFocused --- cypress/e2e/DoenetML/tagSpecific/callaction.cy.js | 2 +- cypress/e2e/DoenetML/tagSpecific/triggerset.cy.js | 2 +- cypress/e2e/DoenetML/tagSpecific/updatevalue.cy.js | 2 +- src/Core/components/CallAction.js | 10 +++++----- src/Core/components/TriggerSet.js | 10 +++++----- src/Core/components/UpdateValue.js | 10 +++++----- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/cypress/e2e/DoenetML/tagSpecific/callaction.cy.js b/cypress/e2e/DoenetML/tagSpecific/callaction.cy.js index 4e368bbc8c..5c63be52e3 100644 --- a/cypress/e2e/DoenetML/tagSpecific/callaction.cy.js +++ b/cypress/e2e/DoenetML/tagSpecific/callaction.cy.js @@ -1297,7 +1297,7 @@ describe('CallAction Tag Tests', function () {

-

+

`}, "*"); diff --git a/cypress/e2e/DoenetML/tagSpecific/triggerset.cy.js b/cypress/e2e/DoenetML/tagSpecific/triggerset.cy.js index 5302ac05dc..80e3967af4 100644 --- a/cypress/e2e/DoenetML/tagSpecific/triggerset.cy.js +++ b/cypress/e2e/DoenetML/tagSpecific/triggerset.cy.js @@ -735,7 +735,7 @@ describe('TriggerSet Tag Tests', function () { x y - + diff --git a/cypress/e2e/DoenetML/tagSpecific/updatevalue.cy.js b/cypress/e2e/DoenetML/tagSpecific/updatevalue.cy.js index 0d82e5b77e..4cc3ad8b04 100644 --- a/cypress/e2e/DoenetML/tagSpecific/updatevalue.cy.js +++ b/cypress/e2e/DoenetML/tagSpecific/updatevalue.cy.js @@ -837,7 +837,7 @@ describe('UpdateValue Tag Tests', function () { x - + `}, "*"); }); cy.get('#\\/_text1').should('have.text', 'a') //wait for page to load diff --git a/src/Core/components/CallAction.js b/src/Core/components/CallAction.js index dbf250f90e..40517155f3 100644 --- a/src/Core/components/CallAction.js +++ b/src/Core/components/CallAction.js @@ -58,7 +58,7 @@ export default class CallAction extends InlineComponent { createTargetComponentNames: true, } - attributes.triggerWhenMouseDownOnObjects = { + attributes.triggerWhenObjectsFocused = { createTargetComponentNames: "string" } @@ -173,9 +173,9 @@ export default class CallAction extends InlineComponent { dependencyType: "attributeTargetComponentNames", attributeName: "triggerWhenObjectsClicked" }, - triggerWhenMouseDownOnObjects: { + triggerWhenObjectsFocused: { dependencyType: "attributeTargetComponentNames", - attributeName: "triggerWhenMouseDownOnObjects" + attributeName: "triggerWhenObjectsFocused" }, triggerWhen: { dependencyType: "attributeComponent", @@ -202,8 +202,8 @@ export default class CallAction extends InlineComponent { triggerWith.push({ target: nameObj.absoluteName, triggeringAction: "click" }) } } - if (dependencyValues.triggerWhenMouseDownOnObjects !== null) { - for (let nameObj of dependencyValues.triggerWhenMouseDownOnObjects) { + if (dependencyValues.triggerWhenObjectsFocused !== null) { + for (let nameObj of dependencyValues.triggerWhenObjectsFocused) { triggerWith.push({ target: nameObj.absoluteName, triggeringAction: "down" }) } } diff --git a/src/Core/components/TriggerSet.js b/src/Core/components/TriggerSet.js index 54b7247db0..5c94ebb9bd 100644 --- a/src/Core/components/TriggerSet.js +++ b/src/Core/components/TriggerSet.js @@ -31,7 +31,7 @@ export default class triggerSet extends InlineComponent { createTargetComponentNames: true, } - attributes.triggerWhenMouseDownOnObjects = { + attributes.triggerWhenObjectsFocused = { createTargetComponentNames: "string" } @@ -86,9 +86,9 @@ export default class triggerSet extends InlineComponent { dependencyType: "attributeTargetComponentNames", attributeName: "triggerWhenObjectsClicked" }, - triggerWhenMouseDownOnObjects: { + triggerWhenObjectsFocused: { dependencyType: "attributeTargetComponentNames", - attributeName: "triggerWhenMouseDownOnObjects" + attributeName: "triggerWhenObjectsFocused" }, triggerWhen: { dependencyType: "attributeComponent", @@ -110,8 +110,8 @@ export default class triggerSet extends InlineComponent { triggerWith.push({ target: nameObj.absoluteName, triggeringAction: "click" }) } } - if (dependencyValues.triggerWhenMouseDownOnObjects !== null) { - for (let nameObj of dependencyValues.triggerWhenMouseDownOnObjects) { + if (dependencyValues.triggerWhenObjectsFocused !== null) { + for (let nameObj of dependencyValues.triggerWhenObjectsFocused) { triggerWith.push({ target: nameObj.absoluteName, triggeringAction: "down" }) } } diff --git a/src/Core/components/UpdateValue.js b/src/Core/components/UpdateValue.js index 490115e6b9..f3d71d220d 100644 --- a/src/Core/components/UpdateValue.js +++ b/src/Core/components/UpdateValue.js @@ -64,7 +64,7 @@ export default class UpdateValue extends InlineComponent { createTargetComponentNames: "string" } - attributes.triggerWhenMouseDownOnObjects = { + attributes.triggerWhenObjectsFocused = { createTargetComponentNames: "string" } @@ -329,9 +329,9 @@ export default class UpdateValue extends InlineComponent { dependencyType: "attributeTargetComponentNames", attributeName: "triggerWhenObjectsClicked" }, - triggerWhenMouseDownOnObjects: { + triggerWhenObjectsFocused: { dependencyType: "attributeTargetComponentNames", - attributeName: "triggerWhenMouseDownOnObjects" + attributeName: "triggerWhenObjectsFocused" }, triggerWhen: { dependencyType: "attributeComponent", @@ -358,8 +358,8 @@ export default class UpdateValue extends InlineComponent { triggerWith.push({ target: nameObj.absoluteName, triggeringAction: "click" }) } } - if (dependencyValues.triggerWhenMouseDownOnObjects !== null) { - for (let nameObj of dependencyValues.triggerWhenMouseDownOnObjects) { + if (dependencyValues.triggerWhenObjectsFocused !== null) { + for (let nameObj of dependencyValues.triggerWhenObjectsFocused) { triggerWith.push({ target: nameObj.absoluteName, triggeringAction: "down" }) } } From 568adae1fbbe57ad3bbf3b24455d8a8ca6f61f4f Mon Sep 17 00:00:00 2001 From: Duane Nykamp Date: Fri, 24 Feb 2023 22:25:14 +0000 Subject: [PATCH 06/13] added triggering utility functions to reduce duplicate code --- src/Core/components/CallAction.js | 160 +-------------------------- src/Core/components/TriggerSet.js | 131 +--------------------- src/Core/components/UpdateValue.js | 159 +-------------------------- src/Core/utils/triggering.js | 168 +++++++++++++++++++++++++++++ 4 files changed, 183 insertions(+), 435 deletions(-) create mode 100644 src/Core/utils/triggering.js diff --git a/src/Core/components/CallAction.js b/src/Core/components/CallAction.js index 40517155f3..248e974aea 100644 --- a/src/Core/components/CallAction.js +++ b/src/Core/components/CallAction.js @@ -1,5 +1,6 @@ import { deepClone } from '../utils/deepFunctions'; import { returnLabelStateVariableDefinitions } from '../utils/label'; +import { addStandardTriggeringStateVariableDefinitions, returnStandardTriggeringAttributes } from '../utils/triggering'; import InlineComponent from './abstract/InlineComponent'; export default class CallAction extends InlineComponent { @@ -43,24 +44,9 @@ export default class CallAction extends InlineComponent { public: true, }; - attributes.triggerWhen = { - createComponentOfType: "boolean", - createStateVariable: "triggerWhen", - defaultValue: false, - triggerActionOnChange: "callActionIfTriggerNewlyTrue" - } - - attributes.triggerWith = { - createTargetComponentNames: true, - } - - attributes.triggerWhenObjectsClicked = { - createTargetComponentNames: true, - } + let triggerAttributes = returnStandardTriggeringAttributes("callActionIfTriggerNewlyTrue") - attributes.triggerWhenObjectsFocused = { - createTargetComponentNames: "string" - } + Object.assign(attributes, triggerAttributes); attributes.numbers = { createComponentOfType: "numberList", @@ -89,6 +75,8 @@ export default class CallAction extends InlineComponent { let stateVariableDefinitions = super.returnStateVariableDefinitions(); + addStandardTriggeringStateVariableDefinitions(stateVariableDefinitions, "callAction"); + let labelDefinitions = returnLabelStateVariableDefinitions(); Object.assign(stateVariableDefinitions, labelDefinitions); @@ -146,144 +134,6 @@ export default class CallAction extends InlineComponent { } - stateVariableDefinitions.insideTriggerSet = { - returnDependencies: () => ({ - parentTriggerSet: { - dependencyType: "parentStateVariable", - parentComponentType: "triggerSet", - variableName: "updateValueAndActionsToTrigger" - }, - }), - definition({ dependencyValues }) { - return { - setValue: { - insideTriggerSet: dependencyValues.parentTriggerSet !== null - } - } - } - } - - stateVariableDefinitions.triggerWith = { - returnDependencies: () => ({ - triggerWith: { - dependencyType: "attributeTargetComponentNames", - attributeName: "triggerWith" - }, - triggerWhenObjectsClicked: { - dependencyType: "attributeTargetComponentNames", - attributeName: "triggerWhenObjectsClicked" - }, - triggerWhenObjectsFocused: { - dependencyType: "attributeTargetComponentNames", - attributeName: "triggerWhenObjectsFocused" - }, - triggerWhen: { - dependencyType: "attributeComponent", - attributeName: "triggerWhen" - }, - insideTriggerSet: { - dependencyType: "stateVariable", - variableName: "insideTriggerSet" - } - }), - definition({ dependencyValues }) { - if (dependencyValues.triggerWhen || dependencyValues.insideTriggerSet) { - return { setValue: { triggerWith: null } } - } else { - - let triggerWith = []; - if (dependencyValues.triggerWith !== null) { - for (let nameObj of dependencyValues.triggerWith) { - triggerWith.push({ target: nameObj.absoluteName }); - } - } - if (dependencyValues.triggerWhenObjectsClicked !== null) { - for (let nameObj of dependencyValues.triggerWhenObjectsClicked) { - triggerWith.push({ target: nameObj.absoluteName, triggeringAction: "click" }) - } - } - if (dependencyValues.triggerWhenObjectsFocused !== null) { - for (let nameObj of dependencyValues.triggerWhenObjectsFocused) { - triggerWith.push({ target: nameObj.absoluteName, triggeringAction: "down" }) - } - } - - if (triggerWith.length === 0) { - triggerWith = null; - } - - return { setValue: { triggerWith } } - } - } - } - - stateVariableDefinitions.triggerWithTargetIds = { - chainActionOnActionOfStateVariableTargets: { - triggeredAction: "callAction" - }, - returnDependencies: () => ({ - triggerWith: { - dependencyType: "stateVariable", - variableName: "triggerWith" - } - }), - definition({ dependencyValues }) { - let triggerWithTargetIds = []; - - if (dependencyValues.triggerWith) { - for (let targetObj of dependencyValues.triggerWith) { - - let id = targetObj.target; - - if (targetObj.triggeringAction) { - id += "|" + targetObj.triggeringAction; - }; - - if (!triggerWithTargetIds.includes(id)) { - triggerWithTargetIds.push(id); - } - - } - } - - return { setValue: { triggerWithTargetIds } } - }, - markStale() { - return { updateActionChaining: true } - } - } - - - let originalHiddenReturnDependencies = stateVariableDefinitions.hidden.returnDependencies; - let originalHiddenDefinition = stateVariableDefinitions.hidden.definition; - - stateVariableDefinitions.hidden.returnDependencies = function (args) { - let dependencies = originalHiddenReturnDependencies(args); - dependencies.triggerWhen = { - dependencyType: "attributeComponent", - attributeName: "triggerWhen" - }; - dependencies.triggerWith = { - dependencyType: "stateVariable", - variableName: "triggerWith" - } - dependencies.insideTriggerSet = { - dependencyType: "stateVariable", - variableName: "insideTriggerSet" - } - return dependencies; - } - - stateVariableDefinitions.hidden.definition = function (args) { - if (args.dependencyValues.triggerWhen || - args.dependencyValues.triggerWith || - args.dependencyValues.insideTriggerSet - ) { - return { setValue: { hidden: true } } - } else { - return originalHiddenDefinition(args); - } - } return stateVariableDefinitions; diff --git a/src/Core/components/TriggerSet.js b/src/Core/components/TriggerSet.js index 5c94ebb9bd..0ee913046c 100644 --- a/src/Core/components/TriggerSet.js +++ b/src/Core/components/TriggerSet.js @@ -1,4 +1,5 @@ import { returnLabelStateVariableDefinitions } from '../utils/label'; +import { addStandardTriggeringStateVariableDefinitions, returnStandardTriggeringAttributes } from '../utils/triggering'; import InlineComponent from './abstract/InlineComponent'; export default class triggerSet extends InlineComponent { @@ -16,24 +17,9 @@ export default class triggerSet extends InlineComponent { public: true, }; - attributes.triggerWhen = { - createComponentOfType: "boolean", - createStateVariable: "triggerWhen", - defaultValue: false, - triggerActionOnChange: "triggerActionsIfTriggerNewlyTrue" - } + let triggerAttributes = returnStandardTriggeringAttributes("triggerActionsIfTriggerNewlyTrue") - attributes.triggerWith = { - createTargetComponentNames: true, - } - - attributes.triggerWhenObjectsClicked = { - createTargetComponentNames: true, - } - - attributes.triggerWhenObjectsFocused = { - createTargetComponentNames: "string" - } + Object.assign(attributes, triggerAttributes); return attributes; } @@ -56,6 +42,8 @@ export default class triggerSet extends InlineComponent { let stateVariableDefinitions = super.returnStateVariableDefinitions(); + addStandardTriggeringStateVariableDefinitions(stateVariableDefinitions, "triggerActions"); + let labelDefinitions = returnLabelStateVariableDefinitions(); Object.assign(stateVariableDefinitions, labelDefinitions); @@ -76,115 +64,6 @@ export default class triggerSet extends InlineComponent { } } - stateVariableDefinitions.triggerWith = { - returnDependencies: () => ({ - triggerWith: { - dependencyType: "attributeTargetComponentNames", - attributeName: "triggerWith" - }, - triggerWhenObjectsClicked: { - dependencyType: "attributeTargetComponentNames", - attributeName: "triggerWhenObjectsClicked" - }, - triggerWhenObjectsFocused: { - dependencyType: "attributeTargetComponentNames", - attributeName: "triggerWhenObjectsFocused" - }, - triggerWhen: { - dependencyType: "attributeComponent", - attributeName: "triggerWhen" - } - }), - definition({ dependencyValues }) { - if (dependencyValues.triggerWhen) { - return { setValue: { triggerWith: null } } - } else { - let triggerWith = []; - if (dependencyValues.triggerWith !== null) { - for (let nameObj of dependencyValues.triggerWith) { - triggerWith.push({ target: nameObj.absoluteName }); - } - } - if (dependencyValues.triggerWhenObjectsClicked !== null) { - for (let nameObj of dependencyValues.triggerWhenObjectsClicked) { - triggerWith.push({ target: nameObj.absoluteName, triggeringAction: "click" }) - } - } - if (dependencyValues.triggerWhenObjectsFocused !== null) { - for (let nameObj of dependencyValues.triggerWhenObjectsFocused) { - triggerWith.push({ target: nameObj.absoluteName, triggeringAction: "down" }) - } - } - - if (triggerWith.length === 0) { - triggerWith = null; - } - - return { setValue: { triggerWith } } - } - } - } - - stateVariableDefinitions.triggerWithTargetIds = { - chainActionOnActionOfStateVariableTargets: { - triggeredAction: "triggerActions" - }, - returnDependencies: () => ({ - triggerWith: { - dependencyType: "stateVariable", - variableName: "triggerWith" - } - }), - definition({ dependencyValues }) { - let triggerWithTargetIds = []; - - if (dependencyValues.triggerWith) { - for (let targetObj of dependencyValues.triggerWith) { - - let id = targetObj.target; - - if (targetObj.triggeringAction) { - id += "|" + targetObj.triggeringAction; - }; - - if (!triggerWithTargetIds.includes(id)) { - triggerWithTargetIds.push(id); - } - - } - } - - return { setValue: { triggerWithTargetIds } } - }, - markStale() { - return { updateActionChaining: true } - }, - } - - - let originalHiddenReturnDependencies = stateVariableDefinitions.hidden.returnDependencies; - let originalHiddenDefinition = stateVariableDefinitions.hidden.definition; - - stateVariableDefinitions.hidden.returnDependencies = function (args) { - let dependencies = originalHiddenReturnDependencies(args); - dependencies.triggerWhen = { - dependencyType: "attributeComponent", - attributeName: "triggerWhen" - }, - dependencies.triggerWith = { - dependencyType: "stateVariable", - variableName: "triggerWith" - } - return dependencies; - } - - stateVariableDefinitions.hidden.definition = function (args) { - if (args.dependencyValues.triggerWhen || args.dependencyValues.triggerWith) { - return { setValue: { hidden: true } } - } else { - return originalHiddenDefinition(args); - } - } return stateVariableDefinitions; diff --git a/src/Core/components/UpdateValue.js b/src/Core/components/UpdateValue.js index f3d71d220d..bbf5578ba8 100644 --- a/src/Core/components/UpdateValue.js +++ b/src/Core/components/UpdateValue.js @@ -1,5 +1,6 @@ import { returnLabelStateVariableDefinitions } from '../utils/label'; import { normalizeMathExpression } from '../utils/math'; +import { addStandardTriggeringStateVariableDefinitions, returnStandardTriggeringAttributes } from '../utils/triggering'; import InlineComponent from './abstract/InlineComponent'; export default class UpdateValue extends InlineComponent { @@ -49,24 +50,11 @@ export default class UpdateValue extends InlineComponent { public: true, }; - attributes.triggerWhen = { - createComponentOfType: "boolean", - createStateVariable: "triggerWhen", - defaultValue: false, - triggerActionOnChange: "updateValueIfTriggerNewlyTrue" - } - attributes.triggerWith = { - createTargetComponentNames: "string" - } + let triggerAttributes = returnStandardTriggeringAttributes("updateValueIfTriggerNewlyTrue") - attributes.triggerWhenObjectsClicked = { - createTargetComponentNames: "string" - } + Object.assign(attributes, triggerAttributes); - attributes.triggerWhenObjectsFocused = { - createTargetComponentNames: "string" - } // for newValue with type==="math" // let simplify="" or simplify="true" be full simplify @@ -98,6 +86,8 @@ export default class UpdateValue extends InlineComponent { let stateVariableDefinitions = super.returnStateVariableDefinitions(); + addStandardTriggeringStateVariableDefinitions(stateVariableDefinitions, "updateValue"); + let labelDefinitions = returnLabelStateVariableDefinitions(); Object.assign(stateVariableDefinitions, labelDefinitions); @@ -302,145 +292,6 @@ export default class UpdateValue extends InlineComponent { }, }; - stateVariableDefinitions.insideTriggerSet = { - returnDependencies: () => ({ - parentTriggerSet: { - dependencyType: "parentStateVariable", - parentComponentType: "triggerSet", - variableName: "updateValueAndActionsToTrigger" - }, - }), - definition({ dependencyValues }) { - return { - setValue: { - insideTriggerSet: dependencyValues.parentTriggerSet !== null - } - } - } - } - - stateVariableDefinitions.triggerWith = { - returnDependencies: () => ({ - triggerWith: { - dependencyType: "attributeTargetComponentNames", - attributeName: "triggerWith" - }, - triggerWhenObjectsClicked: { - dependencyType: "attributeTargetComponentNames", - attributeName: "triggerWhenObjectsClicked" - }, - triggerWhenObjectsFocused: { - dependencyType: "attributeTargetComponentNames", - attributeName: "triggerWhenObjectsFocused" - }, - triggerWhen: { - dependencyType: "attributeComponent", - attributeName: "triggerWhen" - }, - insideTriggerSet: { - dependencyType: "stateVariable", - variableName: "insideTriggerSet" - } - }), - definition({ dependencyValues }) { - if (dependencyValues.triggerWhen || dependencyValues.insideTriggerSet) { - return { setValue: { triggerWith: null } } - } else { - - let triggerWith = []; - if (dependencyValues.triggerWith !== null) { - for (let nameObj of dependencyValues.triggerWith) { - triggerWith.push({ target: nameObj.absoluteName }); - } - } - if (dependencyValues.triggerWhenObjectsClicked !== null) { - for (let nameObj of dependencyValues.triggerWhenObjectsClicked) { - triggerWith.push({ target: nameObj.absoluteName, triggeringAction: "click" }) - } - } - if (dependencyValues.triggerWhenObjectsFocused !== null) { - for (let nameObj of dependencyValues.triggerWhenObjectsFocused) { - triggerWith.push({ target: nameObj.absoluteName, triggeringAction: "down" }) - } - } - - if (triggerWith.length === 0) { - triggerWith = null; - } - - return { setValue: { triggerWith } } - } - } - } - - stateVariableDefinitions.triggerWithTargetIds = { - chainActionOnActionOfStateVariableTargets: { - triggeredAction: "updateValue" - }, - returnDependencies: () => ({ - triggerWith: { - dependencyType: "stateVariable", - variableName: "triggerWith" - } - }), - definition({ dependencyValues }) { - let triggerWithTargetIds = []; - - if (dependencyValues.triggerWith) { - for (let targetObj of dependencyValues.triggerWith) { - - let id = targetObj.target; - - if (targetObj.triggeringAction) { - id += "|" + targetObj.triggeringAction; - }; - - if (!triggerWithTargetIds.includes(id)) { - triggerWithTargetIds.push(id); - } - - } - } - - return { setValue: { triggerWithTargetIds } } - }, - markStale() { - return { updateActionChaining: true } - } - } - - - let originalHiddenReturnDependencies = stateVariableDefinitions.hidden.returnDependencies; - let originalHiddenDefinition = stateVariableDefinitions.hidden.definition; - - stateVariableDefinitions.hidden.returnDependencies = function (args) { - let dependencies = originalHiddenReturnDependencies(args); - dependencies.triggerWhen = { - dependencyType: "attributeComponent", - attributeName: "triggerWhen" - }; - dependencies.triggerWith = { - dependencyType: "stateVariable", - variableName: "triggerWith" - } - dependencies.insideTriggerSet = { - dependencyType: "stateVariable", - variableName: "insideTriggerSet" - } - return dependencies; - } - - stateVariableDefinitions.hidden.definition = function (args) { - if (args.dependencyValues.triggerWhen || - args.dependencyValues.triggerWith || - args.dependencyValues.insideTriggerSet - ) { - return { setValue: { hidden: true } } - } else { - return originalHiddenDefinition(args); - } - } - return stateVariableDefinitions; } diff --git a/src/Core/utils/triggering.js b/src/Core/utils/triggering.js new file mode 100644 index 0000000000..feedd9536f --- /dev/null +++ b/src/Core/utils/triggering.js @@ -0,0 +1,168 @@ +export function returnStandardTriggeringAttributes(triggerActionOnChange) { + + return { + triggerWhen: { + createComponentOfType: "boolean", + createStateVariable: "triggerWhen", + defaultValue: false, + triggerActionOnChange + }, + triggerWith: { + createTargetComponentNames: true, + }, + triggerWhenObjectsClicked: { + createTargetComponentNames: true, + }, + triggerWhenObjectsFocused: { + createTargetComponentNames: true + } + } + +} + + + +export function addStandardTriggeringStateVariableDefinitions(stateVariableDefinitions, triggeredAction) { + + + stateVariableDefinitions.insideTriggerSet = { + returnDependencies: () => ({ + parentTriggerSet: { + dependencyType: "parentStateVariable", + parentComponentType: "triggerSet", + variableName: "updateValueAndActionsToTrigger" + }, + }), + definition({ dependencyValues }) { + return { + setValue: { + insideTriggerSet: dependencyValues.parentTriggerSet !== null + } + } + } + } + + stateVariableDefinitions.triggerWith = { + returnDependencies: () => ({ + triggerWith: { + dependencyType: "attributeTargetComponentNames", + attributeName: "triggerWith" + }, + triggerWhenObjectsClicked: { + dependencyType: "attributeTargetComponentNames", + attributeName: "triggerWhenObjectsClicked" + }, + triggerWhenObjectsFocused: { + dependencyType: "attributeTargetComponentNames", + attributeName: "triggerWhenObjectsFocused" + }, + triggerWhen: { + dependencyType: "attributeComponent", + attributeName: "triggerWhen" + }, + insideTriggerSet: { + dependencyType: "stateVariable", + variableName: "insideTriggerSet" + } + }), + definition({ dependencyValues }) { + if (dependencyValues.triggerWhen || dependencyValues.insideTriggerSet) { + return { setValue: { triggerWith: null } } + } else { + + let triggerWith = []; + if (dependencyValues.triggerWith !== null) { + for (let nameObj of dependencyValues.triggerWith) { + triggerWith.push({ target: nameObj.absoluteName }); + } + } + if (dependencyValues.triggerWhenObjectsClicked !== null) { + for (let nameObj of dependencyValues.triggerWhenObjectsClicked) { + triggerWith.push({ target: nameObj.absoluteName, triggeringAction: "click" }) + } + } + if (dependencyValues.triggerWhenObjectsFocused !== null) { + for (let nameObj of dependencyValues.triggerWhenObjectsFocused) { + triggerWith.push({ target: nameObj.absoluteName, triggeringAction: "down" }) + } + } + + if (triggerWith.length === 0) { + triggerWith = null; + } + + return { setValue: { triggerWith } } + } + } + } + + stateVariableDefinitions.triggerWithTargetIds = { + chainActionOnActionOfStateVariableTargets: { + triggeredAction + }, + returnDependencies: () => ({ + triggerWith: { + dependencyType: "stateVariable", + variableName: "triggerWith" + } + }), + definition({ dependencyValues }) { + let triggerWithTargetIds = []; + + if (dependencyValues.triggerWith) { + for (let targetObj of dependencyValues.triggerWith) { + + let id = targetObj.target; + + if (targetObj.triggeringAction) { + id += "|" + targetObj.triggeringAction; + }; + + if (!triggerWithTargetIds.includes(id)) { + triggerWithTargetIds.push(id); + } + + } + } + + return { setValue: { triggerWithTargetIds } } + }, + markStale() { + return { updateActionChaining: true } + } + } + + + let originalHiddenReturnDependencies = stateVariableDefinitions.hidden.returnDependencies; + let originalHiddenDefinition = stateVariableDefinitions.hidden.definition; + + stateVariableDefinitions.hidden.returnDependencies = function (args) { + let dependencies = originalHiddenReturnDependencies(args); + dependencies.triggerWhen = { + dependencyType: "attributeComponent", + attributeName: "triggerWhen" + }; + dependencies.triggerWith = { + dependencyType: "stateVariable", + variableName: "triggerWith" + } + dependencies.insideTriggerSet = { + dependencyType: "stateVariable", + variableName: "insideTriggerSet" + } + return dependencies; + } + + stateVariableDefinitions.hidden.definition = function (args) { + if (args.dependencyValues.triggerWhen || + args.dependencyValues.triggerWith || + args.dependencyValues.insideTriggerSet + ) { + return { setValue: { hidden: true } } + } else { + return originalHiddenDefinition(args); + } + } + + +} \ No newline at end of file From 1cb7601774480ddc4c32f5eb0221921e1910c638 Mon Sep 17 00:00:00 2001 From: Duane Nykamp Date: Mon, 27 Feb 2023 12:39:15 -0600 Subject: [PATCH 07/13] Rotate image inside a graph (#1923) --- cypress/e2e/DoenetML/tagSpecific/image.cy.js | 54 ++++++++++++++++++++ src/Core/components/Image.js | 8 +++ src/Viewer/renderers/image.jsx | 41 ++++++++++++++- 3 files changed, 102 insertions(+), 1 deletion(-) diff --git a/cypress/e2e/DoenetML/tagSpecific/image.cy.js b/cypress/e2e/DoenetML/tagSpecific/image.cy.js index 42e3626831..9b4d04079f 100644 --- a/cypress/e2e/DoenetML/tagSpecific/image.cy.js +++ b/cypress/e2e/DoenetML/tagSpecific/image.cy.js @@ -484,6 +484,60 @@ describe('Image Tag Tests', function () { }); + it('rotate image in graph', () => { + cy.window().then(async (win) => { + win.postMessage({ + doenetML: ` + a + + + + + +

Rotate 1: $image1.rotate

+

Change rotate 1

+

Change rotate 1a

+ + + + `}, "*"); + }); + + cy.get('#\\/_text1').should('have.text', 'a') //wait for page to load + + // Is there a way to test the rotation of the image in the graph? + + cy.get("#\\/pRotate1").should('contain.text', 'Rotate 1: 0.785') + + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/image1"].stateValues.rotate).eq(Math.PI / 4) + }); + + cy.log("change rotate") + + cy.get('#\\/rotate1 textarea').type("{end}{shift+home}{backspace}3pi/4{enter}", { force: true }) + + cy.get("#\\/pRotate1").should('contain.text', 'Rotate 1: 2.356') + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/image1"].stateValues.rotate).eq(3 * Math.PI / 4) + }); + + cy.get('#\\/rotate1a textarea').type("{end}{shift+home}{backspace}-pi{enter}", { force: true }) + + cy.get("#\\/pRotate1").should('contain.text', 'Rotate 1: -3.14159') + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/image1"].stateValues.rotate).eq(-Math.PI) + }); + + + }); + }) diff --git a/src/Core/components/Image.js b/src/Core/components/Image.js index 394d400897..4fa5020f46 100644 --- a/src/Core/components/Image.js +++ b/src/Core/components/Image.js @@ -107,6 +107,14 @@ export default class Image extends BlockComponent { validValues: ["upperright", "upperleft", "lowerright", "lowerleft", "top", "bottom", "left", "right", "center"] } + attributes.rotate = { + createComponentOfType: "number", + createStateVariable: "rotate", + defaultValue: 0, + public: true, + forRenderer: true, + } + attributes.styleNumber.defaultValue = 0; return attributes; diff --git a/src/Viewer/renderers/image.jsx b/src/Viewer/renderers/image.jsx index feb09d28dc..cea4c6f6ff 100644 --- a/src/Viewer/renderers/image.jsx +++ b/src/Viewer/renderers/image.jsx @@ -29,6 +29,9 @@ export default React.memo(function Image(props) { let currentOffset = useRef(null); + let rotationTransform = useRef(null); + let lastRotate = useRef(SVs.rotate); + const urlOrSource = (SVs.cid ? url : SVs.source) || ""; let onChangeVisibility = isVisible => { @@ -70,7 +73,7 @@ export default React.memo(function Image(props) { visible: !SVs.hidden, fixed, layer: 10 * SVs.layer + 0, - highlight: !fixed + highlight: !fixed, }; @@ -137,6 +140,33 @@ export default React.memo(function Image(props) { let newImageJXG = board.create('image', [urlOrSource, offset, [width, height]], jsxImageAttributes); + // tranformation code copied from jsxgraph documentation: + // https://jsxgraph.uni-bayreuth.de/wiki/index.php?title=Images#The_JavaScript_code_5 + var tOff = board.create('transform', [ + function () { + return -newImageJXG.X() - newImageJXG.W() * 0.5; + }, function () { + return -newImageJXG.Y() - newImageJXG.H() * 0.5; + } + ], { type: 'translate' }); + var tOffInverse = board.create('transform', [ + function () { + return newImageJXG.X() + newImageJXG.W() * 0.5; + }, function () { + return newImageJXG.Y() + newImageJXG.H() * 0.5; + } + ], { type: 'translate' }); + var tRot = board.create('transform', [ + SVs.rotate + ], { type: 'rotate' }); + + + tOff.bindTo(newImageJXG); // Shift image to origin + tRot.bindTo(newImageJXG); // Rotate + tOffInverse.bindTo(newImageJXG); // Shift image back + + rotationTransform.current = tRot; + lastRotate.current = SVs.rotate; newImageJXG.on('down', function (e) { pointerAtDown.current = [e.x, e.y]; @@ -214,6 +244,9 @@ export default React.memo(function Image(props) { previousPositionFromAnchor.current = SVs.positionFromAnchor; currentSize.current = [width, height]; + // need fullUpdate to get initial rotation in case image was from a blob + imageJXG.current.fullUpdate(); + } if (board) { @@ -289,6 +322,12 @@ export default React.memo(function Image(props) { currentSize.current = [width, height]; } + if (SVs.rotate != lastRotate.current) { + rotationTransform.current.setMatrix(board, "rotate", [SVs.rotate]); + lastRotate.current = SVs.rotate; + } + + if (SVs.positionFromAnchor !== previousPositionFromAnchor.current || sizeChanged) { let offset; if (SVs.positionFromAnchor === "center") { From 63e8005abc15c239717140e897d2c7b6237a270d Mon Sep 17 00:00:00 2001 From: Duane Nykamp Date: Mon, 27 Feb 2023 12:40:32 -0600 Subject: [PATCH 08/13] ref in placement exam uses exam tool (#1809) --- src/Viewer/renderers/ref.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Viewer/renderers/ref.jsx b/src/Viewer/renderers/ref.jsx index 010d558e02..46a03c3014 100644 --- a/src/Viewer/renderers/ref.jsx +++ b/src/Viewer/renderers/ref.jsx @@ -87,6 +87,8 @@ export default React.memo(function Ref(props) { url = `tool=editor&${url}`; } url = `/public?${url}` + } else if(pageToolView.page === "placementexam") { + url = `?tool=exam&${url}` } else { url = `?tool=assignment&${url}` } From e8adde874061ccf78c756d87afb36681f05339c8 Mon Sep 17 00:00:00 2001 From: Duane Nykamp Date: Mon, 27 Feb 2023 12:41:01 -0600 Subject: [PATCH 09/13] formula can be an answer response (#1933) --- cypress/e2e/DoenetML/tagSpecific/answer.cy.js | 44 +++++++++++++++++++ src/Core/components/Answer.js | 11 ++++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/cypress/e2e/DoenetML/tagSpecific/answer.cy.js b/cypress/e2e/DoenetML/tagSpecific/answer.cy.js index b9afe8f300..911343747c 100644 --- a/cypress/e2e/DoenetML/tagSpecific/answer.cy.js +++ b/cypress/e2e/DoenetML/tagSpecific/answer.cy.js @@ -21963,4 +21963,48 @@ describe('Answer Tag Tests', function () { }); + it('a function defined by formula uses formula for a response', () => { + cy.window().then(async (win) => { + win.postMessage({ + doenetML: ` + a +

Type the function f(x) = x^2

+

f(x) =

+ + + + $userFormula = $correctFunction + + + +

Submitted responses:

+ + `}, "*"); + }); + + + cy.get("#\\/_p1").should("contain.text", "Type the function"); + + + cy.get("#\\/userFormula textarea").type("x^2{enter}", { force: true }); + + cy.get("#\\/_answer1_submit").click(); + + cy.get("#\\/_answer1_correct").should("be.visible"); + + cy.get("#\\/sr1 .mjx-mrow").eq(0).should("have.text", "x2"); + cy.get("#\\/sr2 .mjx-mrow").eq(0).should("have.text", "x2"); + + cy.get("#\\/userFormula textarea").type("{home}3{enter}", { force: true }); + + cy.get("#\\/_answer1_submit").click(); + + cy.get("#\\/_answer1_incorrect").should("be.visible"); + + cy.get("#\\/sr1 .mjx-mrow").eq(0).should("have.text", "3x2"); + cy.get("#\\/sr2 .mjx-mrow").eq(0).should("have.text", "x2"); + + + }); + }) diff --git a/src/Core/components/Answer.js b/src/Core/components/Answer.js index cea7e2264f..924b6a2f46 100644 --- a/src/Core/components/Answer.js +++ b/src/Core/components/Answer.js @@ -5,6 +5,7 @@ import { serializedComponentsReplacer, serializedComponentsReviver } from '../ut import sha1 from 'crypto-js/sha1'; import Base64 from 'crypto-js/enc-base64'; import stringify from 'json-stringify-deterministic'; +import me from "math-expressions"; export default class Answer extends InlineComponent { static componentType = "answer"; @@ -874,7 +875,7 @@ export default class Answer extends InlineComponent { dependencyType: "descendant", ancestorName: child.componentName, componentTypes: ["_base"], - variableNames: ["isResponse", "value", "values", "componentType"], + variableNames: ["isResponse", "value", "values", "formula", "componentType"], variablesOptional: true, recurseToMatchedChildren: true, includeAttributeChildren: true, @@ -988,9 +989,15 @@ export default class Answer extends InlineComponent { currentResponses.push(...component.stateValues.values) componentType.push(...Array(component.stateValues.values.length) .fill(ct)); - } else { + } else if (component.stateValues.value !== undefined) { currentResponses.push(component.stateValues.value) componentType.push(ct) + } else if (component.stateValues.formula instanceof me.class) { + currentResponses.push(component.stateValues.formula) + componentType.push("math"); + } else { + currentResponses.push(""); + componentType.push("text") } } From ac56ef2ad28319681244c8bd6d854e309e968287 Mon Sep 17 00:00:00 2001 From: Duane Nykamp Date: Mon, 27 Feb 2023 12:58:32 -0600 Subject: [PATCH 10/13] Renderers protect again null children (#1927) --- src/Viewer/renderers/answer.jsx | 7 +- src/Viewer/renderers/figure.jsx | 133 ++++++++++++++++--------------- src/Viewer/renderers/hint.jsx | 70 ++++++++-------- src/Viewer/renderers/section.jsx | 4 +- src/Viewer/renderers/table.jsx | 59 +++++++------- 5 files changed, 138 insertions(+), 135 deletions(-) diff --git a/src/Viewer/renderers/answer.jsx b/src/Viewer/renderers/answer.jsx index cf6b0368a9..056cafdf68 100644 --- a/src/Viewer/renderers/answer.jsx +++ b/src/Viewer/renderers/answer.jsx @@ -4,8 +4,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faCheck, faLevelDownAlt, faTimes, faCloud } from '@fortawesome/free-solid-svg-icons' import styled from 'styled-components'; - // Moved most of checkWorkStyle styling into Button - const Button = styled.button ` +// Moved most of checkWorkStyle styling into Button +const Button = styled.button` position: relative; height: 24px; display: inline-block; @@ -50,7 +50,8 @@ export default React.memo(function Answer(props) { if (SVs.inputChildren.length > 0) { let inputChildNames = SVs.inputChildren.map(x => x.componentName); inputChildrenToRender = children.filter( - child => typeof child !== "string" && inputChildNames.includes(child.props.componentInstructions.componentName) + //child might be null or a string + child => child && typeof child !== "string" && inputChildNames.includes(child.props.componentInstructions.componentName) ) } diff --git a/src/Viewer/renderers/figure.jsx b/src/Viewer/renderers/figure.jsx index 6fe9b8adaf..279ca2450b 100644 --- a/src/Viewer/renderers/figure.jsx +++ b/src/Viewer/renderers/figure.jsx @@ -4,7 +4,7 @@ import VisibilitySensor from 'react-visibility-sensor-v2'; import Measure from 'react-measure'; export default React.memo(function Figure(props) { - let {name, id, SVs, children, actions, callAction} = useDoenetRender(props); + let { name, id, SVs, children, actions, callAction } = useDoenetRender(props); let onChangeVisibility = isVisible => { callAction({ @@ -27,81 +27,82 @@ export default React.memo(function Figure(props) { } // BADBADBAD: need to redo how getting the caption child - // getting it using the internal guts of componentInstructions - // is just asking for trouble - let childrenToRender = children; - let caption = null; // The
element holding the figureName and the caption description - let captionChild = null; // The caption description - - if (SVs.captionChildName) { - let captionChildInd; - for (let [ind, child] of children.entries()) { - if (typeof child !== "string" && child.props.componentInstructions.componentName === SVs.captionChildName) { - captionChildInd = ind; - break; - } - } + // getting it using the internal guts of componentInstructions + // is just asking for trouble + let childrenToRender = children; + let caption = null; // The
element holding the figureName and the caption description + let captionChild = null; // The caption description - captionChild = children[captionChildInd]; - childrenToRender.splice(captionChildInd, 1); // remove caption + if (SVs.captionChildName) { + let captionChildInd; + for (let [ind, child] of children.entries()) { + //child might be null or a string + if (child?.props?.componentInstructions.componentName === SVs.captionChildName) { + captionChildInd = ind; + break; + } } - if (!SVs.suppressFigureNameInCaption) { - let figureName = {SVs.figureName} - if (captionChild) { - caption =
{figureName}: {captionChild}
- } else { - caption =
{figureName}
- } + captionChild = children[captionChildInd]; + childrenToRender.splice(captionChildInd, 1); // remove caption + } + + if (!SVs.suppressFigureNameInCaption) { + let figureName = {SVs.figureName} + if (captionChild) { + caption =
{figureName}: {captionChild}
} else { - if (captionChild) { - caption =
{captionChild}
- } + caption =
{figureName}
} - - const [captionTextAlign, setCaptionTextAlign] = useState("center"); - - // Helper function for countCaptionLines - function getLineHeight(el) { - var temp = document.createElement(el.nodeName), ret; - temp.setAttribute("style", "margin:0; padding:0; " - + "font-family:" + (el.style.fontFamily || "inherit") + "; " - + "font-size:" + (el.style.fontSize || "inherit")); - temp.innerHTML = "A"; - - el.parentNode.appendChild(temp); - ret = temp.clientHeight; - temp.parentNode.removeChild(temp); - - return ret; + } else { + if (captionChild) { + caption =
{captionChild}
} + } - // Helper function for handleResize - // Count the number of lines in the caption - function countCaptionLines() { - var el = document.getElementById(id + "_caption"); - var divHeight = el.offsetHeight; - var lineHeight = getLineHeight(document.getElementById(id + "_caption")); - var lines = Math.round(divHeight / lineHeight); - return lines; - } + const [captionTextAlign, setCaptionTextAlign] = useState("center"); - // Change the display of the caption based on the number of lines in the caption - // Same behavior as LaTeX - function handleResize() { - if (countCaptionLines() >= 2) { - setCaptionTextAlign("left"); // If the caption is 2 or more lines long, it is left-aligned - } else { - setCaptionTextAlign("center"); // Otherwise, it is centered - } + // Helper function for countCaptionLines + function getLineHeight(el) { + var temp = document.createElement(el.nodeName), ret; + temp.setAttribute("style", "margin:0; padding:0; " + + "font-family:" + (el.style.fontFamily || "inherit") + "; " + + "font-size:" + (el.style.fontSize || "inherit")); + temp.innerHTML = "A"; + + el.parentNode.appendChild(temp); + ret = temp.clientHeight; + temp.parentNode.removeChild(temp); + + return ret; + } + + // Helper function for handleResize + // Count the number of lines in the caption + function countCaptionLines() { + var el = document.getElementById(id + "_caption"); + var divHeight = el.offsetHeight; + var lineHeight = getLineHeight(document.getElementById(id + "_caption")); + var lines = Math.round(divHeight / lineHeight); + return lines; + } + + // Change the display of the caption based on the number of lines in the caption + // Same behavior as LaTeX + function handleResize() { + if (countCaptionLines() >= 2) { + setCaptionTextAlign("left"); // If the caption is 2 or more lines long, it is left-aligned + } else { + setCaptionTextAlign("center"); // Otherwise, it is centered } - - return ( - + } + + return ( +
{childrenToRender} -
+
{({ measureRef }) => (
@@ -111,7 +112,7 @@ export default React.memo(function Figure(props) {
-
- ) +
+ ) }) diff --git a/src/Viewer/renderers/hint.jsx b/src/Viewer/renderers/hint.jsx index 8adac1480c..ac6bb7000f 100644 --- a/src/Viewer/renderers/hint.jsx +++ b/src/Viewer/renderers/hint.jsx @@ -9,7 +9,7 @@ import VisibilitySensor from 'react-visibility-sensor-v2'; import styled from 'styled-components'; -const SpanStyling= styled.span` +const SpanStyling = styled.span` &: focus { outline: 2px solid var(--canvastext); outline-offset: 2px; @@ -47,8 +47,8 @@ export default React.memo(function Hint(props) { // is just asking for trouble if (SVs.titleChildName) { for (let [ind, child] of children.entries()) { - //child might be a string - if (child.props?.componentInstructions.componentName === SVs.titleChildName) { + //child might be null or a string + if (child?.props?.componentInstructions.componentName === SVs.titleChildName) { title = children[ind]; children.splice(ind, 1); // remove title break; @@ -71,8 +71,8 @@ export default React.memo(function Hint(props) { action: actions.revealHint, }); }; - let onKeyPressFunction = (e) => { - if(e.key === "Enter"){ + let onKeyPressFunction = (e) => { + if (e.key === "Enter") { callAction({ action: actions.revealHint, }); @@ -82,7 +82,7 @@ export default React.memo(function Hint(props) { let openCloseText = 'open'; if (SVs.open) { - // twirlIcon = ; + // twirlIcon = ; openCloseText = 'close'; icon = ; info = children; @@ -97,13 +97,13 @@ export default React.memo(function Hint(props) { backgroundColor: 'white', }; onKeyPressFunction = (e) => { - if(e.key === "Enter"){ + if (e.key === "Enter") { callAction({ action: actions.closeHint, }); } }; - + onClickFunction = () => { callAction({ action: actions.closeHint, @@ -113,33 +113,33 @@ export default React.memo(function Hint(props) { return ( - + ); }) diff --git a/src/Viewer/renderers/section.jsx b/src/Viewer/renderers/section.jsx index b780c220d4..2f22f82479 100644 --- a/src/Viewer/renderers/section.jsx +++ b/src/Viewer/renderers/section.jsx @@ -56,8 +56,8 @@ export default React.memo(function Section(props) { // is just asking for trouble if (SVs.titleChildName) { for (let [ind, child] of children.entries()) { - //child might be a string - if (child.props?.componentInstructions.componentName === SVs.titleChildName) { + //child might be null or a string + if (child?.props?.componentInstructions.componentName === SVs.titleChildName) { title = children[ind]; children.splice(ind, 1); // remove title break; diff --git a/src/Viewer/renderers/table.jsx b/src/Viewer/renderers/table.jsx index aa9c231828..fc1830ada1 100644 --- a/src/Viewer/renderers/table.jsx +++ b/src/Viewer/renderers/table.jsx @@ -25,47 +25,48 @@ export default React.memo(function Table(props) { return null; } - let heading = null; + let heading = null; - let childrenToRender = [...children]; + let childrenToRender = [...children]; - // BADBADBAD: need to redo how getting the title child - // getting it using the internal guts of componentInstructions - // is just asking for trouble + // BADBADBAD: need to redo how getting the title child + // getting it using the internal guts of componentInstructions + // is just asking for trouble - let title; - if (SVs.titleChildName) { - let titleChildInd; - for (let [ind, child] of children.entries()) { - if (typeof child !== "string" && child.props.componentInstructions.componentName === SVs.titleChildName) { - titleChildInd = ind; - break; - } + let title; + if (SVs.titleChildName) { + let titleChildInd; + for (let [ind, child] of children.entries()) { + //child might be null or a string + if (child?.props?.componentInstructions.componentName === SVs.titleChildName) { + titleChildInd = ind; + break; } - title = children[titleChildInd]; - childrenToRender.splice(titleChildInd, 1); // remove title - } else { - title = SVs.title; } + title = children[titleChildInd]; + childrenToRender.splice(titleChildInd, 1); // remove title + } else { + title = SVs.title; + } - if (!SVs.suppressTableNameInTitle) { - let tableName = {SVs.tableName} - if (title) { - title = <>{tableName}: {title} - } else { - title = tableName; - } + if (!SVs.suppressTableNameInTitle) { + let tableName = {SVs.tableName} + if (title) { + title = <>{tableName}: {title} + } else { + title = tableName; } + } - heading =
{title}
+ heading =
{title}
- return ( - + return ( +
- - ) + + ) }) From 6fc5400378abc2f5bb818c05fb97f34dae580fff Mon Sep 17 00:00:00 2001 From: Duane Nykamp Date: Mon, 27 Feb 2023 13:05:07 -0600 Subject: [PATCH 11/13] bug fix: correctly withhold replacements when shadowing (#1930) --- .../tagSpecific/conditionalcontent.cy.js | 42 +++++++++++++++ src/Core/Core.js | 54 +++++++++++++------ .../components/abstract/CompositeComponent.js | 28 ++++++++++ 3 files changed, 107 insertions(+), 17 deletions(-) diff --git a/cypress/e2e/DoenetML/tagSpecific/conditionalcontent.cy.js b/cypress/e2e/DoenetML/tagSpecific/conditionalcontent.cy.js index 7bee784f7c..bc039b4472 100644 --- a/cypress/e2e/DoenetML/tagSpecific/conditionalcontent.cy.js +++ b/cypress/e2e/DoenetML/tagSpecific/conditionalcontent.cy.js @@ -434,6 +434,48 @@ describe('Conditional Content Tag Tests', function () { }); + it('correctly withhold replacements when shadowing', () => { + cy.window().then(async (win) => { + win.postMessage({ + doenetML: ` +

Hide greeting: + +

+ +

Greeting is hidden: $hide. Greeting: Hello!

+ +

Show copy: + +

+ + $p + + + `}, "*"); + }); + + cy.get('#\\/p').should('have.text', 'Greeting is hidden: false. Greeting: Hello!'); + cy.get('#\\/p2').should('not.exist'); + + cy.get('#\\/hide').click(); + + cy.get('#\\/p').should('have.text', 'Greeting is hidden: true. Greeting: '); + cy.get('#\\/p2').should('not.exist'); + + + cy.get('#\\/show_copy').click(); + cy.get('#\\/p2').should('have.text', 'Greeting is hidden: true. Greeting: '); + + + cy.get('#\\/hide').click(); + + cy.get('#\\/p').should('have.text', 'Greeting is hidden: false. Greeting: Hello!'); + cy.get('#\\/p2').should('have.text', 'Greeting is hidden: false. Greeting: Hello!'); + + + + }) + // tests with cases or else diff --git a/src/Core/Core.js b/src/Core/Core.js index 36c68b2dab..61fb0f6f79 100644 --- a/src/Core/Core.js +++ b/src/Core/Core.js @@ -1953,6 +1953,22 @@ export default class Core { } + + // Call the static function createSerializedReplacements from the composite component + // which returns an object containing a key "replacements" with value an array + // of serialized components that will be turned into real components. + // The replacement components will be used to replace + // the composite itself as children for the composite's parent + // Arguments + // component: the composite component + // components: all components in the document + // workspace: an initially empty object that a composite can use to store information that will then + // be provided when updating composite replacements via calculateReplacementChanges + // componentInfoObjects + // flags + // resolveItem: a function that the composite can use to resolve any state variables + // publicCaseInsensitiveAliasSubstitutions: a function that can be used to find a case insensitive match + // to a public state variable, substituting aliases if necessary let result = await component.constructor.createSerializedReplacements({ component: this.components[component.componentName], // to create proxy components: this.components, @@ -1988,10 +2004,6 @@ export default class Core { serializedReplacements, }); - if (result.withholdReplacements) { - component.replacementsToWithhold = component.replacements.length; - } - } else { throw Error(`Invalid createSerializedReplacements of ${component.componentName}`); } @@ -2236,6 +2248,11 @@ export default class Core { serializedReplacements, }); + if (shadowedComposite.replacementsToWithhold > 0) { + component.replacementsToWithhold = shadowedComposite.replacementsToWithhold; + } + + // record that are finished expanding the composite let targetInd = this.updateInfo.compositesBeingExpanded.indexOf(component.componentName); if (targetInd === -1) { @@ -7197,9 +7214,6 @@ export default class Core { async updateCompositeReplacements({ component, componentChanges, sourceOfUpdate }) { - // TODO: this function is only partially converted to the new system - - // console.log("updateCompositeReplacements " + component.componentName); let deletedComponents = {}; @@ -7234,6 +7248,22 @@ export default class Core { // TODO: why must we evaluate and not just resolve it? await component.stateValues.readyToExpandWhenResolved; + // Call the static function calculateReplacementChanges from the composite component + // which returns the an array of replacement instructions that specify + // changes to the replacements of the composite. + // Arguments + // component: the composite component + // componentChanges: an array of changes made to the replacements of composites during the current update + // that was formerly used by composites to inform their replacement changes but is currently + // not used by any composites. It is retained in case we need this information again. + // components: all components in the document + // workspace: an a composite can use to store information that can be share between + // the initial call to createSerializedReplacements and subsequence calls to calculateReplacementChanges + // componentInfoObjects + // flags + // resolveItem: a function that the composite can use to resolve any state variables + // publicCaseInsensitiveAliasSubstitutions: a function that can be used to find a case insensitive match + // to a public state variable, substituting aliases if necessary const replacementChanges = await component.constructor.calculateReplacementChanges({ component: proxiedComponent, componentChanges, @@ -7449,16 +7479,6 @@ export default class Core { }); - } else if (change.changeType === "moveDependency") { - - // TODO: this is not converted to new system - throw Error('moveDependency not implemented'); - - } else if (change.changeType === "addDependency") { - - // TODO: this is not converted to new system - throw Error('addDependency not implemented'); - } else if (change.changeType === "updateStateVariables") { diff --git a/src/Core/components/abstract/CompositeComponent.js b/src/Core/components/abstract/CompositeComponent.js index e29008b51b..d62ed6fb35 100644 --- a/src/Core/components/abstract/CompositeComponent.js +++ b/src/Core/components/abstract/CompositeComponent.js @@ -55,10 +55,15 @@ export default class CompositeComponent extends BaseComponent { return stateVariableDefinitions; } + // This function is called by Core.js in expandCompositeComponent + // See that invocation for documentation static createSerializedReplacements() { return { replacements: [] } } + + // This function is called by Core.js in updateCompositeReplacements + // See that invocation for documentation static calculateReplacementChanges() { return []; } @@ -86,6 +91,29 @@ export default class CompositeComponent extends BaseComponent { // } + + // The array replacements contains the components that are substituted for the composite + // and become the children of the composite's parent instead of the composite itself. + // Note: components do not set this variable directly. + // Instead, core will create the replacements array based on the information given by + // the static function createSerializedReplacements. + // Core will also change the replacements array based on the instructions returned by + // the static function calculateReplacementChanges. + replacements = []; + + + // The integer replacementsToWithhold is the number of replacements at the end of the replacements array + // that are ignored when inserting replacements as children to composite's parent. + // It is used so that when a composite reduces the number of replacements, we don't need to delete the components, + // but we can just withhold them and reveal them when the number of replacement is increased again. + // For example, if the number of replacements in a sequence is controlled dynamically by a slider, + // the sequence withholds replacements when the number is reduced. + // Note: components do not set this variable directly. + // Instead, core will change the replacementsToWithhold based on the instructions returned by + // the static function calculateReplacementChanges. + replacementsToWithhold = 0; + + get allPotentialRendererTypes() { let allPotentialRendererTypes = super.allPotentialRendererTypes; From a304b276b10594f9da0063824a0750ee7565cd30 Mon Sep 17 00:00:00 2001 From: Duane Nykamp Date: Mon, 27 Feb 2023 13:09:09 -0600 Subject: [PATCH 12/13] EmphasizeRightAngle attribute for angle (#1924) --- cypress/e2e/DoenetML/tagSpecific/angle.cy.js | 46 ++++++++++++++++++++ src/Core/components/Angle.js | 8 ++++ src/Viewer/renderers/angle.jsx | 5 ++- 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/cypress/e2e/DoenetML/tagSpecific/angle.cy.js b/cypress/e2e/DoenetML/tagSpecific/angle.cy.js index 87672fa1a3..3a08971b11 100644 --- a/cypress/e2e/DoenetML/tagSpecific/angle.cy.js +++ b/cypress/e2e/DoenetML/tagSpecific/angle.cy.js @@ -2244,4 +2244,50 @@ describe('Angle Tag Tests', function () { }) + it('emphasize right angle', () => { + cy.window().then(async (win) => { + win.postMessage({ + doenetML: ` + + + + + + +

Emphasize right angle 1:

+

Emphasize right angle 2:

+

Emphasize right angle 3:

+ +

Emphasize right angle: $a1.emphasizeRightAngle, $a2.emphasizeRightAngle, $a3.emphasizeRightAngle

+ + `}, "*"); + }); + + // TODO: How to check renderer itself? + + cy.get('#\\/emphasize').should("have.text", "Emphasize right angle: true, false, false"); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables['/a1'].stateValues.emphasizeRightAngle).eq(true); + expect(stateVariables['/a2'].stateValues.emphasizeRightAngle).eq(false); + expect(stateVariables['/a3'].stateValues.emphasizeRightAngle).eq(false); + }) + + cy.get('#\\/bi1').click(); + cy.get('#\\/bi2').click(); + cy.get('#\\/bi3').click(); + + + cy.get('#\\/emphasize').should("have.text", "Emphasize right angle: false, true, true"); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables['/a1'].stateValues.emphasizeRightAngle).eq(false); + expect(stateVariables['/a2'].stateValues.emphasizeRightAngle).eq(true); + expect(stateVariables['/a3'].stateValues.emphasizeRightAngle).eq(true); + }) + + }) + }); diff --git a/src/Core/components/Angle.js b/src/Core/components/Angle.js index 3eb02475ac..49cfd41cc7 100644 --- a/src/Core/components/Angle.js +++ b/src/Core/components/Angle.js @@ -78,6 +78,14 @@ export default class Angle extends GraphicalComponent { public: true, }; + attributes.emphasizeRightAngle = { + createComponentOfType: "boolean", + createStateVariable: "emphasizeRightAngle", + defaultValue: true, + public: true, + forRenderer: true, + } + return attributes; } diff --git a/src/Viewer/renderers/angle.jsx b/src/Viewer/renderers/angle.jsx index 132d20d0c0..322cbcf6c7 100644 --- a/src/Viewer/renderers/angle.jsx +++ b/src/Viewer/renderers/angle.jsx @@ -55,7 +55,8 @@ export default React.memo(function Angle(props) { radius: SVs.numericalRadius, fillColor: SVs.selectedStyle.fillColor, strokeColor: SVs.selectedStyle.lineColor, - highlight: false + highlight: false, + orthoType: SVs.emphasizeRightAngle ? "square" : "sector", }; jsxAngleAttributes.label = { @@ -150,6 +151,8 @@ export default React.memo(function Angle(props) { previousWithLabel.current = withlabel; } + angleJXG.current.visProp.orthotype = SVs.emphasizeRightAngle ? "square" : "sector"; + angleJXG.current.needsUpdate = true; angleJXG.current.update(); From ff5bfe2c9eadefeea702a4c1c116b8c8da7e5372 Mon Sep 17 00:00:00 2001 From: Duane Nykamp Date: Mon, 27 Feb 2023 13:11:36 -0600 Subject: [PATCH 13/13] ref opens new tab with createButton (#1932) --- src/Viewer/renderers/ref.jsx | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/Viewer/renderers/ref.jsx b/src/Viewer/renderers/ref.jsx index 46a03c3014..dde3ebaed8 100644 --- a/src/Viewer/renderers/ref.jsx +++ b/src/Viewer/renderers/ref.jsx @@ -16,7 +16,7 @@ import styled from 'styled-components'; // } // `; -const RefButton = styled.button ` +const RefButton = styled.button` position: relative; height: 24px; display: inline-block; @@ -130,16 +130,16 @@ export default React.memo(function Ref(props) { if (SVs.createButton) { - if (externalUri) { + if (targetForATag === "_blank") { return
- window.location.href = url} disabled={SVs.disabled}>{SVs.linkText} + window.open(url, targetForATag)} disabled={SVs.disabled}>{SVs.linkText} ; } else { return navigate(url)} disabled={SVs.disabled}>{SVs.linkText} ; } - + } else { if (haveValidTarget) { @@ -147,8 +147,10 @@ export default React.memo(function Ref(props) { // for some reason, if url = "#", the , below, causes a refresh // as it removes the # from the url. So we use a directly in this case. console.log('first case'); - return {linkContent} + return {linkContent} } else { @@ -156,10 +158,12 @@ export default React.memo(function Ref(props) { let stateObj = { fromLink: true } Object.defineProperty(stateObj, 'previousScrollPosition', { get: () => scrollableContainer?.[scrollAttribute], enumerable: true }); console.log('second case'); - return {linkContent} + return {linkContent} } } else {