Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add properties to schema #48

Merged
merged 6 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

133 changes: 91 additions & 42 deletions packages/doenetml-worker/src/Core.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ import {
verifyReplacementsMatchSpecifiedType,
} from "./utils/copy";
import { DependencyHandler } from "./Dependencies";
import { returnDefaultGetArrayKeysFromVarName } from "./utils/stateVariables";
import {
returnDefaultArrayVarNameFromPropIndex,
returnDefaultGetArrayKeysFromVarName,
} from "./utils/stateVariables";
import { nanoid } from "nanoid";
import { get as idb_get, set as idb_set } from "idb-keyval";
import axios from "axios";
Expand Down Expand Up @@ -3647,10 +3650,9 @@ export default class Core {

// add state variable definitions from component class
let newDefinitions =
componentClass.returnNormalizedStateVariableDefinitions({
attributeNames: Object.keys(stateVariableDefinitions),
numerics: this.numerics,
});
componentClass.returnNormalizedStateVariableDefinitions(
this.numerics,
);

Object.assign(stateVariableDefinitions, newDefinitions);

Expand Down Expand Up @@ -3688,7 +3690,7 @@ export default class Core {
let varName = attributeSpecification.createStateVariable;

let stateVarDef = (stateVariableDefinitions[varName] = {
isAttribute: true,
isAttribute: true, // Note: isAttribute is not accessed anywhere
hasEssential: true,
});

Expand Down Expand Up @@ -3957,7 +3959,7 @@ export default class Core {
let varName = attributeSpecification.createStateVariable;

let stateVarDef = (stateVariableDefinitions[varName] = {
isAttribute: true,
isAttribute: true, // Note: isAttribute is not accessed anywhere
hasEssential: true,
});

Expand Down Expand Up @@ -4185,7 +4187,7 @@ export default class Core {
}

let stateVarDef = (stateVariableDefinitions[varName] = {
isAttribute: true,
isAttribute: true, // Note: isAttribute is not accessed anywhere
hasEssential: true,
});

Expand Down Expand Up @@ -5261,6 +5263,7 @@ export default class Core {
if (arrayStateVarObj.shadowingInstructions) {
stateVarObj.shadowingInstructions = {};

// See description of returnWrappingComponents in initializeArrayStateVariable, below.
stateVarObj.wrappingComponents =
arrayStateVarObj.shadowingInstructions.returnWrappingComponents(
arrayEntryPrefix,
Expand Down Expand Up @@ -5500,6 +5503,19 @@ export default class Core {
stateVarObj.numDimensions = 1;
}

let entryPrefixes = stateVarObj.entryPrefixes;

if (!entryPrefixes) {
entryPrefixes = stateVarObj.entryPrefixes = [stateVariable];
}

if (!component.arrayEntryPrefixes) {
component.arrayEntryPrefixes = {};
}
for (let prefix of entryPrefixes) {
component.arrayEntryPrefixes[prefix] = stateVariable;
}

if (stateVarObj.numDimensions > 1) {
// for multiple dimensions, have to convert from arrayKey
// to multi-index when getting or setting
Expand Down Expand Up @@ -5711,21 +5727,23 @@ export default class Core {
};
}

// arrayVarNameFromPropIndex is a function that calculates the name
// an array entry state variable that corresponds to the specified propIndex.
// It is a consequence of retrofitting the ability to index an array (e.g., $a.b[1])
// onto a system that was designed with just array entry variables (e..g, $a.b1).
// arrayVarNameFromPropIndex can be specified in the definition of the array state variable.
// Since numDimensions > 1 here, the default arrayVarNameFromPropIndex
// is to turn $a.b[1][2][3] to $a.p1_2_3,
// where "p" is the first entry prefix of the array "b".

// TODO: if we redesign arrays to be based on indices (or even slices),
// then arrayVarNameFromPropIndex will be obsolete.
if (!stateVarObj.arrayVarNameFromPropIndex) {
stateVarObj.arrayVarNameFromPropIndex = function (
propIndex,
varName,
) {
return (
entryPrefixes[0] +
[
...propIndex.map((x) => Math.round(Number(x))),
...Array(
stateVarObj.numDimensions - propIndex.length,
).fill(1),
].join("_")
stateVarObj.arrayVarNameFromPropIndex =
returnDefaultArrayVarNameFromPropIndex(
stateVarObj.numDimensions,
entryPrefixes[0],
);
};
}

stateVarObj.adjustArrayToNewArraySize = async function () {
Expand Down Expand Up @@ -5809,13 +5827,20 @@ export default class Core {
};
}

// arrayVarNameFromPropIndex is a function that calculates the name
// an array entry state variable that corresponds to the specified propIndex.
// It is a consequence of retrofitting the ability to index an array (e.g., $a.b[1])
// onto a system that was designed with just array entry variables (e..g, $a.b1).
// arrayVarNameFromPropIndex can be specified in the definition of the array state variable.
// Since numDimensions = 1 here, the default arrayVarNameFromPropIndex
// is to turn $a.b[1] to $a.p1,
// where "p" is the first entry prefix of the array "b".

// TODO: if we redesign arrays to be based on indices (or even slices),
// then arrayVarNameFromPropIndex will be obsolete.
if (!stateVarObj.arrayVarNameFromPropIndex) {
stateVarObj.arrayVarNameFromPropIndex = function (
propIndex,
varName,
) {
return entryPrefixes[0] + propIndex[0];
};
stateVarObj.arrayVarNameFromPropIndex =
returnDefaultArrayVarNameFromPropIndex(1, entryPrefixes[0]);
}

stateVarObj.adjustArrayToNewArraySize = async function () {
Expand All @@ -5834,25 +5859,42 @@ export default class Core {
// dimensions, as we just want the string representation
stateVarObj.indexToKey = (index) => String(index);

let entryPrefixes = stateVarObj.entryPrefixes;

if (!entryPrefixes) {
entryPrefixes = stateVarObj.entryPrefixes = [stateVariable];
}

if (!component.arrayEntryPrefixes) {
component.arrayEntryPrefixes = {};
}
for (let prefix of entryPrefixes) {
component.arrayEntryPrefixes[prefix] = stateVariable;
}

if (!stateVarObj.returnEntryDimensions) {
stateVarObj.returnEntryDimensions = () => 1;
stateVarObj.returnEntryDimensions = () => 0;
}

if (stateVarObj.shadowingInstructions) {
// function that returns wrapping components for whole array or entries (if given prefix)
// returnWrappingComponents is a function that returns the wrapping components for
// - the whole array (if called with no arguments), or
// - an array entry (if called with an array entry prefix as the argument)
// It returns wrappingComponents, which is an array of arrays.
// Each inner array corresponds to a dimension of the array,
// starting with the inner dimension,
// so that wrappingComponents[numDimensions-1], if it exists,
// corresponds to the wrapping of the entire array (or array entry),
// leading to the return of a single component.
// Each element of the inner array indicates a wrapping of the corresponding dimension,
// and they are applied in reverse order.
// Each element can be either:
// - a string corresponding to the component type used to wrap
// - an object with fields:
// - componentType: a string corresponding to the component type used to wrap
// - isAttributeNamed: a string giving the name of the attribute that this
// wrapping component should be for the wrapping component immediately preceding
// (no effect if isAttributeNamed appears in the first wrapping component)
// Unless the subsequent wrapping component has been designated isAttributeNamed,
// each wrapping component takes as children either
// - the subsequent wrapping component if it exists,
// - else the original array components.
//
// TODO: wrapping components (like most array features) was designed before
// we had array indexing such as $a.b[1].
// Hence it is based on array entries such as $a.b1, where b is the "prefix".
// $a.b[1] has to be converted to something like $a.b1
// before calculating wrapping components.
// We should rework wrapping components (and other array features)
// to make array indexing (maybe even including slices) be the basis.

if (!stateVarObj.shadowingInstructions.returnWrappingComponents) {
stateVarObj.shadowingInstructions.returnWrappingComponents = (
prefix,
Expand Down Expand Up @@ -6578,6 +6620,13 @@ export default class Core {
});
}

// arrayEntryNamesFromPropIndex is essentially a wrapper around
// stateVarObj.arrayVarNameFromPropIndex.
// (See above description of arrayVarNameFromPropIndex for technical debt commentary.)
// It calls arrayVarNameFromPropIndex on each of an array of stateVariables,
// first creating any missing array entry state variables,
// logs warnings,
// and returns an array of the resulting state variables.
async arrayEntryNamesFromPropIndex({
stateVariables,
component,
Expand Down
3 changes: 2 additions & 1 deletion packages/doenetml-worker/src/Dependencies.js
Original file line number Diff line number Diff line change
Expand Up @@ -3829,7 +3829,8 @@ class StateVariableComponentTypeDependency extends StateVariableDependency {
stateVarObj.componentType;

if (stateVarObj.isArray) {
// if array, use componentType from wrapping components, if exist
// If array, use componentType from wrapping components, if exist.
// See description of returnWrappingComponents in Core.js.
if (stateVarObj.wrappingComponents?.length > 0) {
let wrapCT =
stateVarObj.wrappingComponents[
Expand Down
1 change: 1 addition & 0 deletions packages/doenetml-worker/src/components/Angle.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ export default class Angle extends GraphicalComponent {
isArray: true,
numDimensions: 2,
entryPrefixes: ["pointX", "point"],
returnEntryDimensions: (prefix) => (prefix === "point" ? 1 : 0),
stateVariablesDeterminingDependencies: ["betweenLinesName"],
returnArraySizeDependencies: () => ({}),
returnArraySize() {
Expand Down
3 changes: 2 additions & 1 deletion packages/doenetml-worker/src/components/BestFitLine.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ export default class BestFitLine extends Line {
"point",
{
componentType: "mathList",
isAttribute: "xs",
isAttributeNamed: "xs",
},
],
];
Expand All @@ -220,6 +220,7 @@ export default class BestFitLine extends Line {
isArray: true,
numDimensions: 2,
entryPrefixes: ["datumX", "datum"],
returnEntryDimensions: (prefix) => (prefix === "datum" ? 1 : 0),
getArrayKeysFromVarName({
arrayEntryPrefix,
varEnding,
Expand Down
10 changes: 6 additions & 4 deletions packages/doenetml-worker/src/components/Circle.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ export default class Circle extends Curve {
return GraphicalComponent.returnChildGroups();
}

static returnStateVariableDefinitions(args) {
static returnStateVariableDefinitions(numerics) {
let stateVariableDefinitions =
GraphicalComponent.returnStateVariableDefinitions(args);
GraphicalComponent.returnStateVariableDefinitions(numerics);

Object.assign(
stateVariableDefinitions,
Expand Down Expand Up @@ -436,7 +436,7 @@ export default class Circle extends Curve {
"point",
{
componentType: "mathList",
isAttribute: "xs",
isAttributeNamed: "xs",
},
],
];
Expand All @@ -446,6 +446,8 @@ export default class Circle extends Curve {
isArray: true,
numDimensions: 2,
entryPrefixes: ["throughPointX", "throughPoint"],
returnEntryDimensions: (prefix) =>
prefix === "throughPoint" ? 1 : 0,
getArrayKeysFromVarName({
arrayEntryPrefix,
varEnding,
Expand Down Expand Up @@ -2434,7 +2436,7 @@ export default class Circle extends Curve {
"point",
{
componentType: "mathList",
isAttribute: "xs",
isAttributeNamed: "xs",
},
],
];
Expand Down
21 changes: 9 additions & 12 deletions packages/doenetml-worker/src/components/Copy.js
Original file line number Diff line number Diff line change
Expand Up @@ -2763,6 +2763,7 @@ export async function replacementFromProp({
}
}

// See description of returnWrappingComponents in Core.js.
let wrappingComponents = stateVarObj.wrappingComponents;
let numWrappingComponents = wrappingComponents.length;

Expand Down Expand Up @@ -3161,7 +3162,8 @@ export async function replacementFromProp({
}
}
} else {
// have wrapping components
// Have wrapping components.
// See description of returnWrappingComponents in Core.js.

let createReplacementPiece = async function (
subArrayKeys,
Expand Down Expand Up @@ -3495,9 +3497,9 @@ export async function replacementFromProp({
let attributes = {};

for (let p of pieces) {
if (p.isAttribute) {
let attr = p.isAttribute;
delete p.isAttribute;
if (p.isAttributeNamed) {
let attr = p.isAttributeNamed;
delete p.isAttributeNamed;
attributes[attr] = { component: p };
} else {
children.push(p);
Expand All @@ -3514,14 +3516,9 @@ export async function replacementFromProp({
},
];
if (typeof wrapCs[ind] === "object") {
if (wrapCs[ind].doenetAttributes) {
pieces[0].doenetAttributes = Object.assign(
{},
wrapCs[ind].doenetAttributes,
);
}
if (wrapCs[ind].isAttribute) {
pieces[0].isAttribute = wrapCs[ind].isAttribute;
if (wrapCs[ind].isAttributeNamed) {
pieces[0].isAttributeNamed =
wrapCs[ind].isAttributeNamed;
}
}
}
Expand Down
Loading