@khanacademy/perseus 47.0.1 → 48.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/svg-image.d.ts +0 -4
- package/dist/es/index.js +389 -346
- package/dist/es/index.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +382 -353
- package/dist/index.js.map +1 -1
- package/dist/renderer.d.ts +0 -4
- package/dist/server-item-renderer.d.ts +0 -6
- package/dist/util/graphie-utils.d.ts +35 -0
- package/dist/util/test-utils.d.ts +7 -1
- package/package.json +9 -9
package/dist/index.js
CHANGED
|
@@ -2412,9 +2412,6 @@ function scoreExpression(userInput, rubric, strings, locale) {
|
|
|
2412
2412
|
// We matched a graded answer form, so we can now tell the user
|
|
2413
2413
|
// whether their input was correct or incorrect, and hand out
|
|
2414
2414
|
// points accordingly
|
|
2415
|
-
// TODO(eater): Seems silly to translate result to this
|
|
2416
|
-
// invalid/points thing and immediately translate it back in
|
|
2417
|
-
// ItemRenderer.scoreInput()
|
|
2418
2415
|
return {
|
|
2419
2416
|
type: "points",
|
|
2420
2417
|
earned: matchingAnswerForm.considered === "correct" ? 1 : 0,
|
|
@@ -3363,7 +3360,7 @@ const supportsPassiveEvents = () => {
|
|
|
3363
3360
|
window.addEventListener("testPassive", null, opts);
|
|
3364
3361
|
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
3365
3362
|
window.removeEventListener("testPassive", null, opts);
|
|
3366
|
-
} catch
|
|
3363
|
+
} catch {
|
|
3367
3364
|
// Intentionally left empty!
|
|
3368
3365
|
}
|
|
3369
3366
|
return supportsPassive;
|
|
@@ -3497,6 +3494,163 @@ const Util = {
|
|
|
3497
3494
|
deepClone
|
|
3498
3495
|
};
|
|
3499
3496
|
|
|
3497
|
+
// For offline exercises in the mobile app, we download the graphie data
|
|
3498
|
+
// (svgs and localized data files) and serve them from the local file
|
|
3499
|
+
// system (with file://). We replace urls that start with `web+graphie`
|
|
3500
|
+
// in the perseus json with this `file+graphie` prefix to indicate that
|
|
3501
|
+
// they should have the `file://` protocol instead of `https://`.
|
|
3502
|
+
const svgLocalLabelsRegex = /^file\+graphie:/;
|
|
3503
|
+
const hashRegex = /\/([^/]+)$/;
|
|
3504
|
+
|
|
3505
|
+
/**
|
|
3506
|
+
* A Graphie axis range.
|
|
3507
|
+
*/
|
|
3508
|
+
|
|
3509
|
+
/**
|
|
3510
|
+
* A Graphie label.
|
|
3511
|
+
*/
|
|
3512
|
+
|
|
3513
|
+
/**
|
|
3514
|
+
* The Graphie data (labels, etc.)
|
|
3515
|
+
*/
|
|
3516
|
+
|
|
3517
|
+
function getLocale() {
|
|
3518
|
+
const {
|
|
3519
|
+
JIPT,
|
|
3520
|
+
kaLocale
|
|
3521
|
+
} = getDependencies();
|
|
3522
|
+
return JIPT.useJIPT ? "en-pt" : kaLocale;
|
|
3523
|
+
}
|
|
3524
|
+
function shouldUseLocalizedData() {
|
|
3525
|
+
return getLocale() !== "en";
|
|
3526
|
+
}
|
|
3527
|
+
|
|
3528
|
+
// A regex to split at the last / of a URL, separating the base part from the
|
|
3529
|
+
// hash. This is used to create the localized label data URLs.
|
|
3530
|
+
const splitHashRegex = /\/(?=[^/]+$)/;
|
|
3531
|
+
function getLocalizedDataUrl(url) {
|
|
3532
|
+
// For local (cached) graphie images, they are already localized.
|
|
3533
|
+
if (svgLocalLabelsRegex.test(url)) {
|
|
3534
|
+
return Util.getDataUrl(url);
|
|
3535
|
+
}
|
|
3536
|
+
const [base, hash] = Util.getBaseUrl(url).split(splitHashRegex);
|
|
3537
|
+
return `${base}/${getLocale()}/${hash}-data.json`;
|
|
3538
|
+
}
|
|
3539
|
+
|
|
3540
|
+
// Get the hash from the url, which is just the filename
|
|
3541
|
+
function getUrlHash(url) {
|
|
3542
|
+
const match = url.match(hashRegex);
|
|
3543
|
+
if (match == null) {
|
|
3544
|
+
throw new perseusCore.PerseusError("not a valid URL", perseusCore.Errors.InvalidInput);
|
|
3545
|
+
}
|
|
3546
|
+
return match && match[1];
|
|
3547
|
+
}
|
|
3548
|
+
|
|
3549
|
+
/**
|
|
3550
|
+
* Parse JSONP for a web+graphie://... hash to extract Graphie data, or throw
|
|
3551
|
+
* an error if invalid JSONP.
|
|
3552
|
+
*/
|
|
3553
|
+
function parseDataFromJSONP(graphieHash, graphieJSONP, errorCallback) {
|
|
3554
|
+
// The JSONP is expected to be in the form of `svgDataHASH(...)` or `svgOtherDataHASH(...)`
|
|
3555
|
+
const match = graphieJSONP.match(new RegExp(`^(?:svgData|svgOtherData)${graphieHash}\\((.+)\\);$`));
|
|
3556
|
+
|
|
3557
|
+
// It is also possible that the JSONP is simply a JSON object,
|
|
3558
|
+
// in which case we can parse it directly.
|
|
3559
|
+
const jsonToParse = match ? match[1] : graphieJSONP;
|
|
3560
|
+
|
|
3561
|
+
// Try to parse the JSONP, and if it fails, call the error callback
|
|
3562
|
+
try {
|
|
3563
|
+
return JSON.parse(jsonToParse);
|
|
3564
|
+
} catch (error) {
|
|
3565
|
+
errorCallback(error);
|
|
3566
|
+
return null;
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3569
|
+
// The global cache of label data. Its format is:
|
|
3570
|
+
// {
|
|
3571
|
+
// hash (e.g. "c21435944d2cf0c8f39d9059cb35836aa701d04a"): {
|
|
3572
|
+
// loaded: a boolean of whether the data has been loaded or not
|
|
3573
|
+
// dataCallbacks: a list of callbacks to call with the data when the data
|
|
3574
|
+
// is loaded
|
|
3575
|
+
// data: the other data for this hash
|
|
3576
|
+
// },
|
|
3577
|
+
// ...
|
|
3578
|
+
// }
|
|
3579
|
+
const labelDataCache = {};
|
|
3580
|
+
function loadGraphie(url, onDataLoaded) {
|
|
3581
|
+
const hash = getUrlHash(url);
|
|
3582
|
+
|
|
3583
|
+
// We can't make multiple jsonp calls to the same file because their
|
|
3584
|
+
// callbacks will collide with each other. Instead, we cache the data
|
|
3585
|
+
// and only make the jsonp calls once.
|
|
3586
|
+
const entry = labelDataCache[hash];
|
|
3587
|
+
if (entry != null) {
|
|
3588
|
+
if (entry.loaded) {
|
|
3589
|
+
const {
|
|
3590
|
+
data,
|
|
3591
|
+
localized
|
|
3592
|
+
} = entry;
|
|
3593
|
+
onDataLoaded(data, localized);
|
|
3594
|
+
} else {
|
|
3595
|
+
entry.dataCallbacks.push(onDataLoaded);
|
|
3596
|
+
}
|
|
3597
|
+
} else {
|
|
3598
|
+
const cacheData = {
|
|
3599
|
+
loaded: false,
|
|
3600
|
+
dataCallbacks: [onDataLoaded],
|
|
3601
|
+
localized: shouldUseLocalizedData()
|
|
3602
|
+
};
|
|
3603
|
+
labelDataCache[hash] = cacheData;
|
|
3604
|
+
const retrieveData = async (url, errorCallback) => {
|
|
3605
|
+
const response = await fetch(url);
|
|
3606
|
+
if (!response?.ok) {
|
|
3607
|
+
errorCallback();
|
|
3608
|
+
return;
|
|
3609
|
+
}
|
|
3610
|
+
const jsonp = await response.text();
|
|
3611
|
+
const data = parseDataFromJSONP(hash, jsonp, error => {
|
|
3612
|
+
Log.error("Failed to parse JSONP for svg-image", perseusCore.Errors.Service, {
|
|
3613
|
+
cause: error,
|
|
3614
|
+
loggedMetadata: {
|
|
3615
|
+
dataUrl: Util.getDataUrl(url),
|
|
3616
|
+
jsonp
|
|
3617
|
+
}
|
|
3618
|
+
});
|
|
3619
|
+
});
|
|
3620
|
+
|
|
3621
|
+
// If the data is null, then we failed to parse the JSONP
|
|
3622
|
+
if (!data) {
|
|
3623
|
+
return;
|
|
3624
|
+
}
|
|
3625
|
+
const newCacheEntry = labelDataCache[hash] = {
|
|
3626
|
+
...labelDataCache[hash],
|
|
3627
|
+
loaded: true,
|
|
3628
|
+
data
|
|
3629
|
+
};
|
|
3630
|
+
newCacheEntry.dataCallbacks.forEach(callback => {
|
|
3631
|
+
callback(newCacheEntry.data, cacheData.localized);
|
|
3632
|
+
});
|
|
3633
|
+
};
|
|
3634
|
+
const dataLoadErrorHandler = (x, status, error) => {
|
|
3635
|
+
Log.error("Data load failed for svg-image", perseusCore.Errors.Service, {
|
|
3636
|
+
cause: error,
|
|
3637
|
+
loggedMetadata: {
|
|
3638
|
+
dataUrl: Util.getDataUrl(url),
|
|
3639
|
+
status
|
|
3640
|
+
}
|
|
3641
|
+
});
|
|
3642
|
+
};
|
|
3643
|
+
if (shouldUseLocalizedData()) {
|
|
3644
|
+
cacheData.localized = false;
|
|
3645
|
+
// If there is isn't any localized data, fall back to
|
|
3646
|
+
// the original, unlocalized data
|
|
3647
|
+
retrieveData(getLocalizedDataUrl(url), dataLoadErrorHandler);
|
|
3648
|
+
} else {
|
|
3649
|
+
retrieveData(Util.getDataUrl(url), dataLoadErrorHandler);
|
|
3650
|
+
}
|
|
3651
|
+
}
|
|
3652
|
+
}
|
|
3653
|
+
|
|
3500
3654
|
// Derived from the MIT-licensed:
|
|
3501
3655
|
|
|
3502
3656
|
/*jshint browser:true, node:true */
|
|
@@ -3604,7 +3758,9 @@ function changeViewportTag(contentString, callback) {
|
|
|
3604
3758
|
// finished resetting.
|
|
3605
3759
|
// TODO(jeff, CP-3128): Use Wonder Blocks Timing API.
|
|
3606
3760
|
// eslint-disable-next-line no-restricted-syntax
|
|
3607
|
-
|
|
3761
|
+
if (callback != null) {
|
|
3762
|
+
setTimeout(callback, 0);
|
|
3763
|
+
}
|
|
3608
3764
|
}, 0);
|
|
3609
3765
|
}
|
|
3610
3766
|
|
|
@@ -8926,6 +9082,9 @@ class Graphie$1 {
|
|
|
8926
9082
|
$__default["default"](el).css("position", "relative");
|
|
8927
9083
|
this.raphael = Raphael$1(el);
|
|
8928
9084
|
|
|
9085
|
+
// Hide the Raphael canvas from screen readers
|
|
9086
|
+
$__default["default"](el).attr("aria-hidden", "true");
|
|
9087
|
+
|
|
8929
9088
|
// For a sometimes-reproducible IE8 bug; doesn't affect SVG browsers at all
|
|
8930
9089
|
$__default["default"](el).children("div").css("position", "absolute");
|
|
8931
9090
|
}
|
|
@@ -16964,7 +17123,7 @@ class ImageLoader extends React__namespace.Component {
|
|
|
16964
17123
|
onKeyUp = e => {
|
|
16965
17124
|
// 13 is enter key, 32 is space key
|
|
16966
17125
|
if (e.keyCode === 13 || e.keyCode === 32) {
|
|
16967
|
-
imgProps.onClick
|
|
17126
|
+
imgProps.onClick?.(e);
|
|
16968
17127
|
}
|
|
16969
17128
|
};
|
|
16970
17129
|
onKeyDown = e => {
|
|
@@ -17003,106 +17162,11 @@ class ImageLoader extends React__namespace.Component {
|
|
|
17003
17162
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
17004
17163
|
// Minimum image width to make an image appear as zoomable.
|
|
17005
17164
|
const ZOOMABLE_THRESHOLD = 700;
|
|
17006
|
-
|
|
17007
|
-
// The global cache of label data. Its format is:
|
|
17008
|
-
// {
|
|
17009
|
-
// hash (e.g. "c21435944d2cf0c8f39d9059cb35836aa701d04a"): {
|
|
17010
|
-
// loaded: a boolean of whether the data has been loaded or not
|
|
17011
|
-
// dataCallbacks: a list of callbacks to call with the data when the data
|
|
17012
|
-
// is loaded
|
|
17013
|
-
// data: the other data for this hash
|
|
17014
|
-
// },
|
|
17015
|
-
// ...
|
|
17016
|
-
// }
|
|
17017
|
-
const labelDataCache = {};
|
|
17018
|
-
|
|
17019
|
-
// Write our own JSONP handler because all the other ones don't do things we
|
|
17020
|
-
// need.
|
|
17021
|
-
const doJSONP = function (url, options) {
|
|
17022
|
-
options = {
|
|
17023
|
-
callbackName: "callback",
|
|
17024
|
-
success: $__default["default"].noop,
|
|
17025
|
-
error: $__default["default"].noop,
|
|
17026
|
-
...options
|
|
17027
|
-
};
|
|
17028
|
-
|
|
17029
|
-
// Create the script
|
|
17030
|
-
const script = document.createElement("script");
|
|
17031
|
-
script.setAttribute("async", "");
|
|
17032
|
-
script.setAttribute("src", url);
|
|
17033
|
-
|
|
17034
|
-
// A cleanup function to run when we're done.
|
|
17035
|
-
function cleanup() {
|
|
17036
|
-
document.head && document.head.removeChild(script);
|
|
17037
|
-
delete window[options.callbackName];
|
|
17038
|
-
}
|
|
17039
|
-
|
|
17040
|
-
// Add the global callback.
|
|
17041
|
-
// @ts-expect-error - TS2740 - Type '() => void' is missing the following properties from type 'Window': clientInformation, closed, customElements, devicePixelRatio, and 206 more.
|
|
17042
|
-
window[options.callbackName] = function () {
|
|
17043
|
-
cleanup();
|
|
17044
|
-
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
17045
|
-
args[_key] = arguments[_key];
|
|
17046
|
-
}
|
|
17047
|
-
options.success.apply(null, args);
|
|
17048
|
-
};
|
|
17049
|
-
|
|
17050
|
-
// Add the error handler.
|
|
17051
|
-
script.addEventListener("error", function () {
|
|
17052
|
-
cleanup();
|
|
17053
|
-
for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
|
|
17054
|
-
args[_key2] = arguments[_key2];
|
|
17055
|
-
}
|
|
17056
|
-
options.error.apply(null, args);
|
|
17057
|
-
});
|
|
17058
|
-
|
|
17059
|
-
// Insert the script to start the download.
|
|
17060
|
-
document.head && document.head.appendChild(script);
|
|
17061
|
-
};
|
|
17062
|
-
|
|
17063
|
-
// For offline exercises in the mobile app, we download the graphie data
|
|
17064
|
-
// (svgs and localized data files) and serve them from the local file
|
|
17065
|
-
// system (with file://). We replace urls that start with `web+graphie`
|
|
17066
|
-
// in the perseus json with this `file+graphie` prefix to indicate that
|
|
17067
|
-
// they should have the `file://` protocol instead of `https://`.
|
|
17068
|
-
const svgLocalLabelsRegex = /^file\+graphie:/;
|
|
17069
|
-
const hashRegex = /\/([^/]+)$/;
|
|
17070
17165
|
function isImageProbablyPhotograph(imageUrl) {
|
|
17071
17166
|
// TODO(david): Do an inventory to refine this heuristic. For example, what
|
|
17072
17167
|
// % of .png images are illustrations?
|
|
17073
17168
|
return /\.(jpg|jpeg)$/i.test(imageUrl);
|
|
17074
17169
|
}
|
|
17075
|
-
function getLocale() {
|
|
17076
|
-
const {
|
|
17077
|
-
JIPT,
|
|
17078
|
-
kaLocale
|
|
17079
|
-
} = getDependencies();
|
|
17080
|
-
return JIPT.useJIPT ? "en-pt" : kaLocale;
|
|
17081
|
-
}
|
|
17082
|
-
function shouldUseLocalizedData() {
|
|
17083
|
-
return getLocale() !== "en";
|
|
17084
|
-
}
|
|
17085
|
-
|
|
17086
|
-
// A regex to split at the last / of a URL, separating the base part from the
|
|
17087
|
-
// hash. This is used to create the localized label data URLs.
|
|
17088
|
-
const splitHashRegex = /\/(?=[^/]+$)/;
|
|
17089
|
-
function getLocalizedDataUrl(url) {
|
|
17090
|
-
// For local (cached) graphie images, they are already localized.
|
|
17091
|
-
if (svgLocalLabelsRegex.test(url)) {
|
|
17092
|
-
return Util.getDataUrl(url);
|
|
17093
|
-
}
|
|
17094
|
-
const [base, hash] = Util.getBaseUrl(url).split(splitHashRegex);
|
|
17095
|
-
return `${base}/${getLocale()}/${hash}-data.json`;
|
|
17096
|
-
}
|
|
17097
|
-
|
|
17098
|
-
// Get the hash from the url, which is just the filename
|
|
17099
|
-
function getUrlHash(url) {
|
|
17100
|
-
const match = url.match(hashRegex);
|
|
17101
|
-
if (match == null) {
|
|
17102
|
-
throw new perseusCore.PerseusError("not a valid URL", perseusCore.Errors.InvalidInput);
|
|
17103
|
-
}
|
|
17104
|
-
return match && match[1];
|
|
17105
|
-
}
|
|
17106
17170
|
function defaultPreloader(dimensions) {
|
|
17107
17171
|
return /*#__PURE__*/React__namespace.createElement("span", {
|
|
17108
17172
|
style: {
|
|
@@ -17189,90 +17253,22 @@ class SvgImage extends React__namespace.Component {
|
|
|
17189
17253
|
return Util.isLabeledSVG(this.props.src) ? state.imageLoaded && state.dataLoaded : state.imageLoaded;
|
|
17190
17254
|
}
|
|
17191
17255
|
loadResources() {
|
|
17192
|
-
|
|
17193
|
-
|
|
17194
|
-
|
|
17195
|
-
|
|
17196
|
-
|
|
17197
|
-
if (labelDataCache[hash]) {
|
|
17198
|
-
if (labelDataCache[hash].loaded) {
|
|
17199
|
-
const {
|
|
17200
|
-
data,
|
|
17201
|
-
localized
|
|
17202
|
-
} = labelDataCache[hash];
|
|
17203
|
-
this.onDataLoaded(data, localized);
|
|
17204
|
-
} else {
|
|
17205
|
-
labelDataCache[hash].dataCallbacks.push(this.onDataLoaded);
|
|
17206
|
-
}
|
|
17207
|
-
} else {
|
|
17208
|
-
const cacheData = {
|
|
17209
|
-
loaded: false,
|
|
17210
|
-
dataCallbacks: [this.onDataLoaded],
|
|
17211
|
-
data: null,
|
|
17212
|
-
localized: shouldUseLocalizedData()
|
|
17213
|
-
};
|
|
17214
|
-
labelDataCache[hash] = cacheData;
|
|
17215
|
-
const retrieveData = (url, errorCallback) => {
|
|
17216
|
-
doJSONP(url, {
|
|
17217
|
-
callbackName: "svgData" + hash,
|
|
17218
|
-
success: data => {
|
|
17219
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'data' because it is a read-only property.
|
|
17220
|
-
cacheData.data = data;
|
|
17221
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'loaded' because it is a read-only property.
|
|
17222
|
-
cacheData.loaded = true;
|
|
17223
|
-
___default["default"].each(cacheData.dataCallbacks, callback => {
|
|
17224
|
-
// @ts-expect-error - TS2345 - Argument of type 'null' is not assignable to parameter of type '{ labels: readonly any[]; range: readonly any[]; }'.
|
|
17225
|
-
callback(cacheData.data, cacheData.localized);
|
|
17226
|
-
});
|
|
17227
|
-
},
|
|
17228
|
-
error: errorCallback
|
|
17229
|
-
});
|
|
17230
|
-
};
|
|
17231
|
-
if (shouldUseLocalizedData()) {
|
|
17232
|
-
retrieveData(getLocalizedDataUrl(this.props.src), (x, status, error) => {
|
|
17233
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'localized' because it is a read-only property.
|
|
17234
|
-
cacheData.localized = false;
|
|
17235
|
-
|
|
17236
|
-
// If there is isn't any localized data, fall back to
|
|
17237
|
-
// the original, unlocalized data
|
|
17238
|
-
retrieveData(Util.getDataUrl(this.props.src), (x, status, error) => {
|
|
17239
|
-
Log.error("Data load failed for svg-image", perseusCore.Errors.Service, {
|
|
17240
|
-
cause: error,
|
|
17241
|
-
loggedMetadata: {
|
|
17242
|
-
dataUrl: Util.getDataUrl(this.props.src),
|
|
17243
|
-
status
|
|
17244
|
-
}
|
|
17245
|
-
});
|
|
17246
|
-
});
|
|
17256
|
+
loadGraphie(this.props.src, (data, localized) => {
|
|
17257
|
+
if (this._isMounted && data.labels && data.range) {
|
|
17258
|
+
const labelsRendered = {};
|
|
17259
|
+
data.labels.forEach(label => {
|
|
17260
|
+
labelsRendered[label.content] = false;
|
|
17247
17261
|
});
|
|
17248
|
-
|
|
17249
|
-
|
|
17250
|
-
|
|
17251
|
-
|
|
17252
|
-
|
|
17253
|
-
|
|
17254
|
-
status
|
|
17255
|
-
}
|
|
17256
|
-
});
|
|
17262
|
+
this.setState({
|
|
17263
|
+
dataLoaded: true,
|
|
17264
|
+
labelDataIsLocalized: localized,
|
|
17265
|
+
labelsRendered,
|
|
17266
|
+
labels: data.labels,
|
|
17267
|
+
range: data.range
|
|
17257
17268
|
});
|
|
17258
17269
|
}
|
|
17259
|
-
}
|
|
17270
|
+
});
|
|
17260
17271
|
}
|
|
17261
|
-
onDataLoaded = (data, localized) => {
|
|
17262
|
-
if (this._isMounted && data.labels && data.range) {
|
|
17263
|
-
const labelsRendered = data.labels.reduce((dict, label) => ({
|
|
17264
|
-
...dict,
|
|
17265
|
-
[label.content]: false
|
|
17266
|
-
}), {});
|
|
17267
|
-
this.setState({
|
|
17268
|
-
dataLoaded: true,
|
|
17269
|
-
labelDataIsLocalized: localized,
|
|
17270
|
-
labelsRendered,
|
|
17271
|
-
labels: data.labels,
|
|
17272
|
-
range: data.range
|
|
17273
|
-
});
|
|
17274
|
-
}
|
|
17275
|
-
};
|
|
17276
17272
|
sizeProvided() {
|
|
17277
17273
|
return this.props.width != null && this.props.height != null;
|
|
17278
17274
|
}
|
|
@@ -17396,7 +17392,7 @@ class SvgImage extends React__namespace.Component {
|
|
|
17396
17392
|
$image.width() < this.props.width || this.props.zoomToFullSizeOnMobile) {
|
|
17397
17393
|
ZoomService.handleZoomClick(e, this.props.zoomToFullSizeOnMobile);
|
|
17398
17394
|
}
|
|
17399
|
-
this.props.trackInteraction
|
|
17395
|
+
this.props.trackInteraction?.();
|
|
17400
17396
|
};
|
|
17401
17397
|
handleUpdate = status => {
|
|
17402
17398
|
this.props.onUpdate();
|
|
@@ -19803,8 +19799,8 @@ class WidgetContainer extends React__namespace.Component {
|
|
|
19803
19799
|
};
|
|
19804
19800
|
|
|
19805
19801
|
// Note: if you add more props here, please consider whether or not
|
|
19806
|
-
// it should be auto-serialized
|
|
19807
|
-
// widget-jsonify-deprecated.jsx and widget-prop-denylist.jsx
|
|
19802
|
+
// it should be auto-serialized.
|
|
19803
|
+
// See widget-jsonify-deprecated.jsx and widget-prop-denylist.jsx
|
|
19808
19804
|
|
|
19809
19805
|
// We default to an empty object for style instead of null
|
|
19810
19806
|
// because of a strange bug where the static styles aren't applied
|
|
@@ -21100,15 +21096,6 @@ class Renderer extends React__namespace.Component {
|
|
|
21100
21096
|
return combinedScore;
|
|
21101
21097
|
}
|
|
21102
21098
|
|
|
21103
|
-
/**
|
|
21104
|
-
* @deprecated use scorePerseusItem
|
|
21105
|
-
*/
|
|
21106
|
-
guessAndScore = () => {
|
|
21107
|
-
const totalGuess = this.getUserInput();
|
|
21108
|
-
const totalScore = this.score();
|
|
21109
|
-
return [totalGuess, totalScore];
|
|
21110
|
-
};
|
|
21111
|
-
|
|
21112
21099
|
// TranslationLinter callback
|
|
21113
21100
|
handletranslationLintErrors = lintErrors => {
|
|
21114
21101
|
if (!this._isMounted) {
|
|
@@ -21786,11 +21773,11 @@ class SimpleKeypadInput extends React__namespace.Component {
|
|
|
21786
21773
|
keypadType: mathInput.KeypadType.FRACTION
|
|
21787
21774
|
}, () => {
|
|
21788
21775
|
if (_this._isMounted) {
|
|
21789
|
-
onFocus
|
|
21776
|
+
onFocus?.();
|
|
21790
21777
|
}
|
|
21791
21778
|
});
|
|
21792
21779
|
} else {
|
|
21793
|
-
onFocus
|
|
21780
|
+
onFocus?.();
|
|
21794
21781
|
}
|
|
21795
21782
|
},
|
|
21796
21783
|
value: value == null ? "" : "" + value
|
|
@@ -22029,9 +22016,6 @@ function scoreInputNumber(userInput, rubric, strings) {
|
|
|
22029
22016
|
// If `currentValue` is not TeX, this should be a no-op.
|
|
22030
22017
|
const currentValue = ParseTex$1(userInput.currentValue);
|
|
22031
22018
|
const result = val(currentValue);
|
|
22032
|
-
|
|
22033
|
-
// TODO(eater): Seems silly to translate result to this invalid/points
|
|
22034
|
-
// thing and immediately translate it back in ItemRenderer.scoreInput()
|
|
22035
22019
|
if (result.empty) {
|
|
22036
22020
|
return {
|
|
22037
22021
|
type: "invalid",
|
|
@@ -22395,10 +22379,6 @@ function scoreNumericInput(userInput, rubric, strings) {
|
|
|
22395
22379
|
message: matchedAnswer?.message ?? null,
|
|
22396
22380
|
guess: localValue
|
|
22397
22381
|
};
|
|
22398
|
-
|
|
22399
|
-
// TODO(eater): Seems silly to translate result to this
|
|
22400
|
-
// invalid/points thing and immediately translate it
|
|
22401
|
-
// back in ItemRenderer.scoreInput()
|
|
22402
22382
|
if (result.empty) {
|
|
22403
22383
|
return {
|
|
22404
22384
|
type: "invalid",
|
|
@@ -25255,7 +25235,7 @@ const scrollElementToOffset = function (elementOrWindow, offset) {
|
|
|
25255
25235
|
// TODO(jeff, WEB-1378): Use Wonder Blocks Timing API
|
|
25256
25236
|
// eslint-disable-next-line no-restricted-syntax
|
|
25257
25237
|
clearInterval(scrollInterval);
|
|
25258
|
-
callback
|
|
25238
|
+
callback?.();
|
|
25259
25239
|
}
|
|
25260
25240
|
}, tickLength);
|
|
25261
25241
|
};
|
|
@@ -27317,7 +27297,7 @@ class CSProgram extends React__namespace.Component {
|
|
|
27317
27297
|
let data = {};
|
|
27318
27298
|
try {
|
|
27319
27299
|
data = JSON.parse(e.originalEvent.data);
|
|
27320
|
-
} catch
|
|
27300
|
+
} catch {
|
|
27321
27301
|
return;
|
|
27322
27302
|
}
|
|
27323
27303
|
if (___default["default"].isUndefined(data.testsPassed)) {
|
|
@@ -28344,7 +28324,6 @@ class GradedGroupSet extends React__namespace.Component {
|
|
|
28344
28324
|
currentGroup: 0
|
|
28345
28325
|
};
|
|
28346
28326
|
shouldComponentUpdate(nextProps, nextState) {
|
|
28347
|
-
nextProps.gradedGroups;
|
|
28348
28327
|
return nextProps !== this.props || nextState !== this.state;
|
|
28349
28328
|
}
|
|
28350
28329
|
change = (() => {
|
|
@@ -29868,7 +29847,7 @@ class Iframe extends React__namespace.Component {
|
|
|
29868
29847
|
let data = {};
|
|
29869
29848
|
try {
|
|
29870
29849
|
data = JSON.parse(e.originalEvent.data);
|
|
29871
|
-
} catch
|
|
29850
|
+
} catch {
|
|
29872
29851
|
return;
|
|
29873
29852
|
}
|
|
29874
29853
|
if (___default["default"].isUndefined(data.testsPassed)) {
|
|
@@ -30963,10 +30942,10 @@ class Graph extends React__namespace.Component {
|
|
|
30963
30942
|
this.props.onMouseDown(coord);
|
|
30964
30943
|
}, this) : undefined;
|
|
30965
30944
|
const onMouseOver = $instructionsWrapper ? function () {
|
|
30966
|
-
$instructionsWrapper
|
|
30945
|
+
$instructionsWrapper?.css("opacity", invisible);
|
|
30967
30946
|
} : undefined;
|
|
30968
30947
|
const onMouseOut = $instructionsWrapper ? function () {
|
|
30969
|
-
$instructionsWrapper
|
|
30948
|
+
$instructionsWrapper?.css("opacity", visible);
|
|
30970
30949
|
} : undefined;
|
|
30971
30950
|
/* eslint-enable indent */
|
|
30972
30951
|
|
|
@@ -31679,6 +31658,7 @@ function replaceOutsideTeX(mathString) {
|
|
|
31679
31658
|
const parsed = mathOnlyParser(mathString);
|
|
31680
31659
|
let result = "";
|
|
31681
31660
|
for (const piece of parsed) {
|
|
31661
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
31682
31662
|
piece.type === "math" ? result += "$" + piece.content + "$" : piece.type === "specialCharacter" ? result += escapeIfUnescaped(piece.content) : result += piece.content;
|
|
31683
31663
|
}
|
|
31684
31664
|
return `\\text{${result}}`;
|
|
@@ -34650,42 +34630,64 @@ function renderPointGraph(state, dispatch) {
|
|
|
34650
34630
|
};
|
|
34651
34631
|
}
|
|
34652
34632
|
function PointGraph(props) {
|
|
34653
|
-
const
|
|
34633
|
+
const {
|
|
34634
|
+
numPoints
|
|
34635
|
+
} = props.graphState;
|
|
34636
|
+
const graphConfig = useGraphConfig();
|
|
34637
|
+
const pointsRef = React__namespace.useRef([]);
|
|
34638
|
+
|
|
34639
|
+
// Dimensions to build the graph overlay for Unlimited Point.
|
|
34640
|
+
const {
|
|
34641
|
+
range: [x, y]
|
|
34642
|
+
} = graphConfig;
|
|
34643
|
+
const [[left, top]] = useTransformVectorsToPixels([x[0], y[1]]);
|
|
34644
|
+
|
|
34645
|
+
// This useEffect is to handle the focus snapping for Unlimited Point.
|
|
34646
|
+
React__namespace.useEffect(() => {
|
|
34647
|
+
const focusedIndex = props.graphState.focusedPointIndex;
|
|
34648
|
+
if (focusedIndex != null) {
|
|
34649
|
+
pointsRef.current[focusedIndex]?.focus();
|
|
34650
|
+
}
|
|
34651
|
+
}, [props.graphState.focusedPointIndex, pointsRef]);
|
|
34652
|
+
const statefulProps = {
|
|
34653
|
+
...props,
|
|
34654
|
+
graphConfig,
|
|
34655
|
+
pointsRef,
|
|
34656
|
+
top,
|
|
34657
|
+
left
|
|
34658
|
+
};
|
|
34654
34659
|
if (numPoints === "unlimited") {
|
|
34655
|
-
return UnlimitedPointGraph(
|
|
34660
|
+
return UnlimitedPointGraph(statefulProps);
|
|
34656
34661
|
}
|
|
34657
|
-
return LimitedPointGraph(
|
|
34662
|
+
return LimitedPointGraph(statefulProps);
|
|
34658
34663
|
}
|
|
34659
|
-
function LimitedPointGraph(
|
|
34664
|
+
function LimitedPointGraph(statefulProps) {
|
|
34660
34665
|
const {
|
|
34661
34666
|
dispatch
|
|
34662
|
-
} =
|
|
34663
|
-
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null,
|
|
34667
|
+
} = statefulProps;
|
|
34668
|
+
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, statefulProps.graphState.coords.map((point, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
|
|
34664
34669
|
key: i,
|
|
34665
34670
|
point: point,
|
|
34666
34671
|
sequenceNumber: i + 1,
|
|
34667
34672
|
onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination))
|
|
34668
34673
|
})));
|
|
34669
34674
|
}
|
|
34670
|
-
function UnlimitedPointGraph(
|
|
34675
|
+
function UnlimitedPointGraph(statefulProps) {
|
|
34671
34676
|
const {
|
|
34672
|
-
dispatch
|
|
34673
|
-
|
|
34674
|
-
|
|
34677
|
+
dispatch,
|
|
34678
|
+
graphConfig,
|
|
34679
|
+
pointsRef,
|
|
34680
|
+
top,
|
|
34681
|
+
left
|
|
34682
|
+
} = statefulProps;
|
|
34683
|
+
const {
|
|
34684
|
+
coords
|
|
34685
|
+
} = statefulProps.graphState;
|
|
34675
34686
|
const {
|
|
34676
|
-
|
|
34687
|
+
graphDimensionsInPixels
|
|
34677
34688
|
} = graphConfig;
|
|
34678
|
-
const
|
|
34679
|
-
const
|
|
34680
|
-
const [[widthPx, heightPx]] = useTransformDimensionsToPixels([width, height]);
|
|
34681
|
-
const [[left, top]] = useTransformVectorsToPixels([minX, maxY]);
|
|
34682
|
-
const itemsRef = React__namespace.useRef([]);
|
|
34683
|
-
React__namespace.useEffect(() => {
|
|
34684
|
-
const focusedIndex = props.graphState.focusedPointIndex;
|
|
34685
|
-
if (focusedIndex != null) {
|
|
34686
|
-
itemsRef.current[focusedIndex]?.focus();
|
|
34687
|
-
}
|
|
34688
|
-
}, [props.graphState.focusedPointIndex, itemsRef]);
|
|
34689
|
+
const widthPx = graphDimensionsInPixels[0];
|
|
34690
|
+
const heightPx = graphDimensionsInPixels[1];
|
|
34689
34691
|
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("rect", {
|
|
34690
34692
|
style: {
|
|
34691
34693
|
fill: "rgba(0,0,0,0)",
|
|
@@ -34702,13 +34704,13 @@ function UnlimitedPointGraph(props) {
|
|
|
34702
34704
|
const graphCoordinates = pixelsToVectors([[x, y]], graphConfig);
|
|
34703
34705
|
dispatch(actions.pointGraph.addPoint(graphCoordinates[0]));
|
|
34704
34706
|
}
|
|
34705
|
-
}),
|
|
34707
|
+
}), coords.map((point, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
|
|
34706
34708
|
key: i,
|
|
34707
34709
|
point: point,
|
|
34708
34710
|
sequenceNumber: i + 1,
|
|
34709
34711
|
onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination)),
|
|
34710
34712
|
ref: ref => {
|
|
34711
|
-
|
|
34713
|
+
pointsRef.current[i] = ref;
|
|
34712
34714
|
},
|
|
34713
34715
|
onFocus: () => {
|
|
34714
34716
|
dispatch(actions.pointGraph.focusPoint(i));
|
|
@@ -34759,36 +34761,42 @@ function renderPolygonGraph(state, dispatch) {
|
|
|
34759
34761
|
interactiveElementsDescription: null
|
|
34760
34762
|
};
|
|
34761
34763
|
}
|
|
34762
|
-
const
|
|
34763
|
-
const [hovered, setHovered] = React__namespace.useState(false);
|
|
34764
|
-
// This is more so required for the re-rendering that occurs when state
|
|
34765
|
-
// updates; specifically with regard to line weighting and polygon focus.
|
|
34766
|
-
const [focusVisible, setFocusVisible] = React__namespace.useState(false);
|
|
34764
|
+
const PolygonGraph = props => {
|
|
34767
34765
|
const {
|
|
34768
34766
|
dispatch
|
|
34769
34767
|
} = props;
|
|
34770
34768
|
const {
|
|
34769
|
+
numSides,
|
|
34771
34770
|
coords,
|
|
34772
|
-
showAngles,
|
|
34773
|
-
showSides,
|
|
34774
|
-
range,
|
|
34775
34771
|
snapStep,
|
|
34776
34772
|
snapTo = "grid"
|
|
34777
34773
|
} = props.graphState;
|
|
34774
|
+
const graphConfig = useGraphConfig();
|
|
34775
|
+
|
|
34776
|
+
// Ref to implement the dragging behavior on a Limited/Closed Polygon.
|
|
34777
|
+
const polygonRef = React__namespace.useRef(null);
|
|
34778
|
+
// Ref to manage dynamic focus for the points in Unlimited Polygon
|
|
34779
|
+
const pointsRef = React__namespace.useRef([]);
|
|
34780
|
+
// Ref to manage the last move time for a Limited Polygon.
|
|
34781
|
+
const lastMoveTimeRef = React__namespace.useRef(0);
|
|
34782
|
+
|
|
34783
|
+
// Dimensions to build the graph overlay for Unlimited Polgyon.
|
|
34778
34784
|
const {
|
|
34779
|
-
|
|
34780
|
-
} =
|
|
34785
|
+
range: [x, y]
|
|
34786
|
+
} = graphConfig;
|
|
34787
|
+
const [[left, top]] = useTransformVectorsToPixels([x[0], y[1]]);
|
|
34781
34788
|
|
|
34782
34789
|
// TODO(benchristel): can the default set of points be removed here? I don't
|
|
34783
34790
|
// think coords can be null.
|
|
34784
34791
|
const points = coords ?? [[0, 0]];
|
|
34785
|
-
|
|
34792
|
+
|
|
34793
|
+
// Logic to build the dragging experience. Primarily used by Limited Polygon.
|
|
34786
34794
|
const dragReferencePoint = points[0];
|
|
34787
34795
|
const constrain = ["angles", "sides"].includes(snapTo) ? p => p : p => snap(snapStep, p);
|
|
34788
34796
|
const {
|
|
34789
34797
|
dragging
|
|
34790
34798
|
} = useDraggable({
|
|
34791
|
-
gestureTarget:
|
|
34799
|
+
gestureTarget: polygonRef,
|
|
34792
34800
|
point: dragReferencePoint,
|
|
34793
34801
|
onMove: newPoint => {
|
|
34794
34802
|
const delta = mafs.vec.sub(newPoint, dragReferencePoint);
|
|
@@ -34796,7 +34804,61 @@ const LimitedPolygonGraph = props => {
|
|
|
34796
34804
|
},
|
|
34797
34805
|
constrainKeyboardMovement: constrain
|
|
34798
34806
|
});
|
|
34799
|
-
|
|
34807
|
+
|
|
34808
|
+
// Hover/focus state for Limited Polgyon effects.
|
|
34809
|
+
const [hovered, setHovered] = React__namespace.useState(false);
|
|
34810
|
+
// This is more so required for the re-rendering that occurs when state
|
|
34811
|
+
// updates; specifically with regard to line weighting and polygon focus.
|
|
34812
|
+
const [focusVisible, setFocusVisible] = React__namespace.useState(false);
|
|
34813
|
+
|
|
34814
|
+
// This useEffect is to handle the focus snapping for Unlimited Polygon.
|
|
34815
|
+
React__namespace.useEffect(() => {
|
|
34816
|
+
const focusedIndex = props.graphState.focusedPointIndex;
|
|
34817
|
+
if (focusedIndex != null) {
|
|
34818
|
+
pointsRef.current[focusedIndex]?.focus();
|
|
34819
|
+
}
|
|
34820
|
+
}, [props.graphState.focusedPointIndex, pointsRef]);
|
|
34821
|
+
const statefulProps = {
|
|
34822
|
+
...props,
|
|
34823
|
+
graphConfig,
|
|
34824
|
+
polygonRef,
|
|
34825
|
+
pointsRef,
|
|
34826
|
+
lastMoveTimeRef,
|
|
34827
|
+
left,
|
|
34828
|
+
top,
|
|
34829
|
+
dragging,
|
|
34830
|
+
points,
|
|
34831
|
+
constrain,
|
|
34832
|
+
hovered,
|
|
34833
|
+
setHovered,
|
|
34834
|
+
focusVisible,
|
|
34835
|
+
setFocusVisible
|
|
34836
|
+
};
|
|
34837
|
+
return numSides === "unlimited" ? UnlimitedPolygonGraph(statefulProps) : LimitedPolygonGraph(statefulProps);
|
|
34838
|
+
};
|
|
34839
|
+
const LimitedPolygonGraph = statefulProps => {
|
|
34840
|
+
const {
|
|
34841
|
+
dispatch,
|
|
34842
|
+
hovered,
|
|
34843
|
+
setHovered,
|
|
34844
|
+
focusVisible,
|
|
34845
|
+
setFocusVisible,
|
|
34846
|
+
graphConfig,
|
|
34847
|
+
polygonRef,
|
|
34848
|
+
lastMoveTimeRef,
|
|
34849
|
+
dragging,
|
|
34850
|
+
points,
|
|
34851
|
+
constrain
|
|
34852
|
+
} = statefulProps;
|
|
34853
|
+
const {
|
|
34854
|
+
showAngles,
|
|
34855
|
+
showSides,
|
|
34856
|
+
range,
|
|
34857
|
+
snapTo = "grid"
|
|
34858
|
+
} = statefulProps.graphState;
|
|
34859
|
+
const {
|
|
34860
|
+
disableKeyboardInteraction
|
|
34861
|
+
} = graphConfig;
|
|
34800
34862
|
const lines = getLines(points);
|
|
34801
34863
|
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Polygon, {
|
|
34802
34864
|
points: [...points],
|
|
@@ -34835,7 +34897,7 @@ const LimitedPolygonGraph = props => {
|
|
|
34835
34897
|
points: [...points],
|
|
34836
34898
|
color: "transparent",
|
|
34837
34899
|
svgPolygonProps: {
|
|
34838
|
-
ref,
|
|
34900
|
+
ref: polygonRef,
|
|
34839
34901
|
tabIndex: disableKeyboardInteraction ? -1 : 0,
|
|
34840
34902
|
strokeWidth: TARGET_SIZE,
|
|
34841
34903
|
style: {
|
|
@@ -34847,13 +34909,13 @@ const LimitedPolygonGraph = props => {
|
|
|
34847
34909
|
// Required to remove line weighting when user clicks away
|
|
34848
34910
|
// from the focused polygon
|
|
34849
34911
|
onKeyDownCapture: () => {
|
|
34850
|
-
setFocusVisible(hasFocusVisible(
|
|
34912
|
+
setFocusVisible(hasFocusVisible(polygonRef.current));
|
|
34851
34913
|
},
|
|
34852
34914
|
// Required for lines to darken on focus
|
|
34853
|
-
onFocus: () => setFocusVisible(hasFocusVisible(
|
|
34915
|
+
onFocus: () => setFocusVisible(hasFocusVisible(polygonRef.current)),
|
|
34854
34916
|
// Required for line weighting to update on blur. Without this,
|
|
34855
34917
|
// the user has to hover over the shape for it to update
|
|
34856
|
-
onBlur: () => setFocusVisible(hasFocusVisible(
|
|
34918
|
+
onBlur: () => setFocusVisible(hasFocusVisible(polygonRef.current)),
|
|
34857
34919
|
className: "movable-polygon"
|
|
34858
34920
|
}
|
|
34859
34921
|
}), points.map((point, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
|
|
@@ -34865,94 +34927,86 @@ const LimitedPolygonGraph = props => {
|
|
|
34865
34927
|
const now = Date.now();
|
|
34866
34928
|
const targetFPS = 40;
|
|
34867
34929
|
const moveThresholdTime = 1000 / targetFPS;
|
|
34868
|
-
if (now -
|
|
34930
|
+
if (now - lastMoveTimeRef.current > moveThresholdTime) {
|
|
34869
34931
|
dispatch(actions.polygon.movePoint(i, destination));
|
|
34870
|
-
|
|
34932
|
+
lastMoveTimeRef.current = now;
|
|
34871
34933
|
}
|
|
34872
34934
|
}
|
|
34873
34935
|
})));
|
|
34874
34936
|
};
|
|
34875
|
-
const UnlimitedPolygonGraph =
|
|
34937
|
+
const UnlimitedPolygonGraph = statefulProps => {
|
|
34876
34938
|
const {
|
|
34877
|
-
dispatch
|
|
34878
|
-
|
|
34939
|
+
dispatch,
|
|
34940
|
+
graphConfig,
|
|
34941
|
+
left,
|
|
34942
|
+
top,
|
|
34943
|
+
pointsRef,
|
|
34944
|
+
points
|
|
34945
|
+
} = statefulProps;
|
|
34879
34946
|
const {
|
|
34880
34947
|
coords,
|
|
34881
34948
|
closedPolygon
|
|
34882
|
-
} =
|
|
34883
|
-
const graphConfig = useGraphConfig();
|
|
34884
|
-
const {
|
|
34885
|
-
range: [x, y],
|
|
34886
|
-
graphDimensionsInPixels
|
|
34887
|
-
} = graphConfig;
|
|
34888
|
-
const widthPx = graphDimensionsInPixels[0];
|
|
34889
|
-
const heightPx = graphDimensionsInPixels[1];
|
|
34890
|
-
const [[left, top]] = useTransformVectorsToPixels([x[0], y[1]]);
|
|
34891
|
-
const pointRef = React__namespace.useRef([]);
|
|
34949
|
+
} = statefulProps.graphState;
|
|
34892
34950
|
|
|
34893
|
-
//
|
|
34894
|
-
// think coords can be null.
|
|
34895
|
-
const points = coords ?? [[0, 0]];
|
|
34896
|
-
React__namespace.useEffect(() => {
|
|
34897
|
-
const focusedIndex = props.graphState.focusedPointIndex;
|
|
34898
|
-
if (focusedIndex != null) {
|
|
34899
|
-
pointRef.current[focusedIndex]?.focus();
|
|
34900
|
-
}
|
|
34901
|
-
}, [props.graphState.focusedPointIndex, pointRef]);
|
|
34951
|
+
// If the polygon is closed, return a LimitedPolygon component.
|
|
34902
34952
|
if (closedPolygon) {
|
|
34903
34953
|
const closedPolygonProps = {
|
|
34904
|
-
...
|
|
34954
|
+
...statefulProps,
|
|
34905
34955
|
numSides: coords.length
|
|
34906
34956
|
};
|
|
34907
34957
|
return /*#__PURE__*/React__namespace.createElement(LimitedPolygonGraph, closedPolygonProps);
|
|
34908
|
-
}
|
|
34909
|
-
|
|
34958
|
+
}
|
|
34959
|
+
const {
|
|
34960
|
+
graphDimensionsInPixels
|
|
34961
|
+
} = graphConfig;
|
|
34962
|
+
const widthPx = graphDimensionsInPixels[0];
|
|
34963
|
+
const heightPx = graphDimensionsInPixels[1];
|
|
34964
|
+
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("rect", {
|
|
34965
|
+
style: {
|
|
34966
|
+
fill: "rgba(0,0,0,0)",
|
|
34967
|
+
cursor: "crosshair"
|
|
34968
|
+
},
|
|
34969
|
+
width: widthPx,
|
|
34970
|
+
height: heightPx,
|
|
34971
|
+
x: left,
|
|
34972
|
+
y: top,
|
|
34973
|
+
onClick: event => {
|
|
34974
|
+
const elementRect = event.currentTarget.getBoundingClientRect();
|
|
34975
|
+
const x = event.clientX - elementRect.x;
|
|
34976
|
+
const y = event.clientY - elementRect.y;
|
|
34977
|
+
const graphCoordinates = pixelsToVectors([[x, y]], graphConfig);
|
|
34978
|
+
dispatch(actions.polygon.addPoint(graphCoordinates[0]));
|
|
34979
|
+
}
|
|
34980
|
+
}), /*#__PURE__*/React__namespace.createElement(mafs.Polyline, {
|
|
34981
|
+
points: [...points],
|
|
34982
|
+
color: "var(--movable-line-stroke-color)",
|
|
34983
|
+
svgPolylineProps: {
|
|
34984
|
+
strokeWidth: "var(--movable-line-stroke-weight)",
|
|
34910
34985
|
style: {
|
|
34911
|
-
fill: "
|
|
34912
|
-
cursor: "crosshair"
|
|
34913
|
-
},
|
|
34914
|
-
width: widthPx,
|
|
34915
|
-
height: heightPx,
|
|
34916
|
-
x: left,
|
|
34917
|
-
y: top,
|
|
34918
|
-
onClick: event => {
|
|
34919
|
-
const elementRect = event.currentTarget.getBoundingClientRect();
|
|
34920
|
-
const x = event.clientX - elementRect.x;
|
|
34921
|
-
const y = event.clientY - elementRect.y;
|
|
34922
|
-
const graphCoordinates = pixelsToVectors([[x, y]], graphConfig);
|
|
34923
|
-
dispatch(actions.polygon.addPoint(graphCoordinates[0]));
|
|
34924
|
-
}
|
|
34925
|
-
}), /*#__PURE__*/React__namespace.createElement(mafs.Polyline, {
|
|
34926
|
-
points: [...points],
|
|
34927
|
-
color: "var(--movable-line-stroke-color)",
|
|
34928
|
-
svgPolylineProps: {
|
|
34929
|
-
strokeWidth: "var(--movable-line-stroke-weight)",
|
|
34930
|
-
style: {
|
|
34931
|
-
fill: "transparent"
|
|
34932
|
-
}
|
|
34986
|
+
fill: "transparent"
|
|
34933
34987
|
}
|
|
34934
|
-
}
|
|
34935
|
-
|
|
34936
|
-
|
|
34937
|
-
|
|
34938
|
-
|
|
34939
|
-
|
|
34940
|
-
|
|
34941
|
-
|
|
34942
|
-
|
|
34943
|
-
|
|
34944
|
-
|
|
34945
|
-
|
|
34946
|
-
|
|
34947
|
-
|
|
34948
|
-
|
|
34949
|
-
|
|
34950
|
-
|
|
34951
|
-
|
|
34952
|
-
dispatch(actions.polygon.clickPoint(i));
|
|
34988
|
+
}
|
|
34989
|
+
}), coords.map((point, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
|
|
34990
|
+
key: i,
|
|
34991
|
+
point: point,
|
|
34992
|
+
sequenceNumber: i + 1,
|
|
34993
|
+
onMove: destination => dispatch(actions.polygon.movePoint(i, destination)),
|
|
34994
|
+
ref: ref => {
|
|
34995
|
+
pointsRef.current[i] = ref;
|
|
34996
|
+
},
|
|
34997
|
+
onFocus: () => {
|
|
34998
|
+
dispatch(actions.polygon.focusPoint(i));
|
|
34999
|
+
},
|
|
35000
|
+
onClick: () => {
|
|
35001
|
+
// If the point being clicked is the first point and
|
|
35002
|
+
// there's enough points to form a polygon (3 or more)
|
|
35003
|
+
// Close the shape before setting focus.
|
|
35004
|
+
if (i === 0 && coords.length >= 3) {
|
|
35005
|
+
dispatch(actions.polygon.closePolygon());
|
|
34953
35006
|
}
|
|
34954
|
-
|
|
34955
|
-
|
|
35007
|
+
dispatch(actions.polygon.clickPoint(i));
|
|
35008
|
+
}
|
|
35009
|
+
})));
|
|
34956
35010
|
};
|
|
34957
35011
|
function getLines(points) {
|
|
34958
35012
|
return points.map((point, i) => {
|
|
@@ -34964,17 +35018,13 @@ const hasFocusVisible = element => {
|
|
|
34964
35018
|
const matches = selector => element?.matches(selector) ?? false;
|
|
34965
35019
|
try {
|
|
34966
35020
|
return matches(":focus-visible");
|
|
34967
|
-
} catch
|
|
35021
|
+
} catch {
|
|
34968
35022
|
// jsdom doesn't support :focus-visible
|
|
34969
35023
|
// (see https://github.com/jsdom/jsdom/issues/3426),
|
|
34970
35024
|
// so the call to matches(":focus-visible") will fail in tests.
|
|
34971
35025
|
return matches(":focus");
|
|
34972
35026
|
}
|
|
34973
35027
|
};
|
|
34974
|
-
const PolygonGraph = props => {
|
|
34975
|
-
const numSides = props.graphState.numSides;
|
|
34976
|
-
return numSides === "unlimited" ? UnlimitedPolygonGraph(props) : LimitedPolygonGraph(props);
|
|
34977
|
-
};
|
|
34978
35028
|
|
|
34979
35029
|
function renderQuadraticGraph(state, dispatch) {
|
|
34980
35030
|
return {
|
|
@@ -40395,7 +40445,7 @@ class Sortable extends React__namespace.Component {
|
|
|
40395
40445
|
this.setState({
|
|
40396
40446
|
items
|
|
40397
40447
|
}, () => {
|
|
40398
|
-
this.props.onMeasure
|
|
40448
|
+
this.props.onMeasure?.({
|
|
40399
40449
|
widths: widths,
|
|
40400
40450
|
heights: heights
|
|
40401
40451
|
});
|
|
@@ -40445,7 +40495,7 @@ class Sortable extends React__namespace.Component {
|
|
|
40445
40495
|
this.setState({
|
|
40446
40496
|
items: nextItems
|
|
40447
40497
|
});
|
|
40448
|
-
this.props.onChange
|
|
40498
|
+
this.props.onChange?.({});
|
|
40449
40499
|
}
|
|
40450
40500
|
onMouseMove(key) {
|
|
40451
40501
|
// Dragging: Rearrange items based on draggable's position
|
|
@@ -40525,7 +40575,7 @@ class Sortable extends React__namespace.Component {
|
|
|
40525
40575
|
// HACK: We need to know *that* the widget changed, but currently it's
|
|
40526
40576
|
// not set up in a nice way to tell us *how* it changed, since the
|
|
40527
40577
|
// permutation of the items is stored in state.
|
|
40528
|
-
this.props.onChange
|
|
40578
|
+
this.props.onChange?.({});
|
|
40529
40579
|
});
|
|
40530
40580
|
|
|
40531
40581
|
// @ts-expect-error - TS2339 - Property 'animationFrameRequest' does not exist on type 'Sortable'.
|
|
@@ -43250,7 +43300,7 @@ class Card extends React__namespace.Component {
|
|
|
43250
43300
|
dragging: true
|
|
43251
43301
|
});
|
|
43252
43302
|
this.bindMouseMoveUp();
|
|
43253
|
-
this.props.onMouseDown
|
|
43303
|
+
this.props.onMouseDown?.(loc, this);
|
|
43254
43304
|
}
|
|
43255
43305
|
};
|
|
43256
43306
|
onMouseMove = event => {
|
|
@@ -43260,7 +43310,7 @@ class Card extends React__namespace.Component {
|
|
|
43260
43310
|
event.preventDefault();
|
|
43261
43311
|
const loc = Util.extractPointerLocation(event);
|
|
43262
43312
|
if (loc) {
|
|
43263
|
-
this.props.onMouseMove
|
|
43313
|
+
this.props.onMouseMove?.(loc);
|
|
43264
43314
|
}
|
|
43265
43315
|
};
|
|
43266
43316
|
onMouseUp = event => {
|
|
@@ -43271,7 +43321,7 @@ class Card extends React__namespace.Component {
|
|
|
43271
43321
|
dragging: false
|
|
43272
43322
|
});
|
|
43273
43323
|
this.unbindMouseMoveUp();
|
|
43274
|
-
this.props.onMouseUp
|
|
43324
|
+
this.props.onMouseUp?.(loc);
|
|
43275
43325
|
}
|
|
43276
43326
|
};
|
|
43277
43327
|
render() {
|
|
@@ -45477,7 +45527,7 @@ const init = function () {
|
|
|
45477
45527
|
|
|
45478
45528
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
45479
45529
|
const libName = "@khanacademy/perseus";
|
|
45480
|
-
const libVersion = "
|
|
45530
|
+
const libVersion = "48.1.0";
|
|
45481
45531
|
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
45482
45532
|
|
|
45483
45533
|
const apiVersion = {
|
|
@@ -46237,28 +46287,6 @@ class ServerItemRenderer extends React__namespace.Component {
|
|
|
46237
46287
|
return this.questionRenderer.getUserInputMap();
|
|
46238
46288
|
}
|
|
46239
46289
|
|
|
46240
|
-
/**
|
|
46241
|
-
* Grades the item.
|
|
46242
|
-
*
|
|
46243
|
-
* @deprecated use scorePerseusItem
|
|
46244
|
-
*/
|
|
46245
|
-
scoreInput() {
|
|
46246
|
-
const guess = this.getUserInput();
|
|
46247
|
-
const score = scorePerseusItem(this.props.item.question, guess, this.context.strings, this.context.locale);
|
|
46248
|
-
|
|
46249
|
-
// Continue to include an empty guess for the now defunct answer area.
|
|
46250
|
-
// TODO(alex): Check whether we rely on the format here for
|
|
46251
|
-
// analyzing ProblemLogs. If not, remove this layer.
|
|
46252
|
-
const maxCompatGuess = [this.questionRenderer.getUserInput(), []];
|
|
46253
|
-
const keScore = keScoreFromPerseusScore(score, maxCompatGuess, this.questionRenderer.getSerializedState());
|
|
46254
|
-
const emptyQuestionAreaWidgets = this.questionRenderer.emptyWidgets();
|
|
46255
|
-
this.setState({
|
|
46256
|
-
questionCompleted: keScore.correct,
|
|
46257
|
-
questionHighlightedWidgets: emptyQuestionAreaWidgets
|
|
46258
|
-
});
|
|
46259
|
-
return keScore;
|
|
46260
|
-
}
|
|
46261
|
-
|
|
46262
46290
|
/**
|
|
46263
46291
|
* Returns an array of all widget IDs in the order they occur in
|
|
46264
46292
|
* the question content.
|
|
@@ -49165,6 +49193,7 @@ exports.lockedFigureFillStyles = lockedFigureFillStyles;
|
|
|
49165
49193
|
exports.makeSafeUrl = makeSafeUrl;
|
|
49166
49194
|
exports.mathOnlyParser = mathOnlyParser;
|
|
49167
49195
|
exports.mediaQueries = mediaQueries;
|
|
49196
|
+
exports.parseDataFromJSONP = parseDataFromJSONP;
|
|
49168
49197
|
exports.parsePerseusItem = parsePerseusItem;
|
|
49169
49198
|
exports.plotterPlotTypes = plotterPlotTypes;
|
|
49170
49199
|
exports.preprocessTex = preprocessTex;
|