@khanacademy/perseus 48.0.0 → 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 +173 -171
- package/dist/es/index.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +174 -176
- package/dist/index.js.map +1 -1
- package/dist/util/graphie-utils.d.ts +35 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -81,6 +81,8 @@ export { addWidget, QUESTION_WIDGETS } from "./util/snowman-utils";
|
|
|
81
81
|
export { getCircleCoords, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getSegmentCoords, getSinusoidCoords, getQuadraticCoords, getAngleCoords, } from "./widgets/interactive-graphs/reducer/initialize-graph-state";
|
|
82
82
|
export { getClockwiseAngle } from "./widgets/interactive-graphs/math";
|
|
83
83
|
export { makeSafeUrl } from "./widgets/phet-simulation";
|
|
84
|
+
export { parseDataFromJSONP } from "./util/graphie-utils";
|
|
85
|
+
export type { GraphieData, GraphieLabel, GraphieRange, } from "./util/graphie-utils";
|
|
84
86
|
/**
|
|
85
87
|
* Mixins
|
|
86
88
|
*/
|
package/dist/index.js
CHANGED
|
@@ -3494,6 +3494,163 @@ const Util = {
|
|
|
3494
3494
|
deepClone
|
|
3495
3495
|
};
|
|
3496
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
|
+
|
|
3497
3654
|
// Derived from the MIT-licensed:
|
|
3498
3655
|
|
|
3499
3656
|
/*jshint browser:true, node:true */
|
|
@@ -8925,6 +9082,9 @@ class Graphie$1 {
|
|
|
8925
9082
|
$__default["default"](el).css("position", "relative");
|
|
8926
9083
|
this.raphael = Raphael$1(el);
|
|
8927
9084
|
|
|
9085
|
+
// Hide the Raphael canvas from screen readers
|
|
9086
|
+
$__default["default"](el).attr("aria-hidden", "true");
|
|
9087
|
+
|
|
8928
9088
|
// For a sometimes-reproducible IE8 bug; doesn't affect SVG browsers at all
|
|
8929
9089
|
$__default["default"](el).children("div").css("position", "absolute");
|
|
8930
9090
|
}
|
|
@@ -17002,106 +17162,11 @@ class ImageLoader extends React__namespace.Component {
|
|
|
17002
17162
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
17003
17163
|
// Minimum image width to make an image appear as zoomable.
|
|
17004
17164
|
const ZOOMABLE_THRESHOLD = 700;
|
|
17005
|
-
|
|
17006
|
-
// The global cache of label data. Its format is:
|
|
17007
|
-
// {
|
|
17008
|
-
// hash (e.g. "c21435944d2cf0c8f39d9059cb35836aa701d04a"): {
|
|
17009
|
-
// loaded: a boolean of whether the data has been loaded or not
|
|
17010
|
-
// dataCallbacks: a list of callbacks to call with the data when the data
|
|
17011
|
-
// is loaded
|
|
17012
|
-
// data: the other data for this hash
|
|
17013
|
-
// },
|
|
17014
|
-
// ...
|
|
17015
|
-
// }
|
|
17016
|
-
const labelDataCache = {};
|
|
17017
|
-
|
|
17018
|
-
// Write our own JSONP handler because all the other ones don't do things we
|
|
17019
|
-
// need.
|
|
17020
|
-
const doJSONP = function (url, options) {
|
|
17021
|
-
options = {
|
|
17022
|
-
callbackName: "callback",
|
|
17023
|
-
success: $__default["default"].noop,
|
|
17024
|
-
error: $__default["default"].noop,
|
|
17025
|
-
...options
|
|
17026
|
-
};
|
|
17027
|
-
|
|
17028
|
-
// Create the script
|
|
17029
|
-
const script = document.createElement("script");
|
|
17030
|
-
script.setAttribute("async", "");
|
|
17031
|
-
script.setAttribute("src", url);
|
|
17032
|
-
|
|
17033
|
-
// A cleanup function to run when we're done.
|
|
17034
|
-
function cleanup() {
|
|
17035
|
-
document.head?.removeChild(script);
|
|
17036
|
-
delete window[options.callbackName];
|
|
17037
|
-
}
|
|
17038
|
-
|
|
17039
|
-
// Add the global callback.
|
|
17040
|
-
// @ts-expect-error - TS2740 - Type '() => void' is missing the following properties from type 'Window': clientInformation, closed, customElements, devicePixelRatio, and 206 more.
|
|
17041
|
-
window[options.callbackName] = function () {
|
|
17042
|
-
cleanup();
|
|
17043
|
-
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
17044
|
-
args[_key] = arguments[_key];
|
|
17045
|
-
}
|
|
17046
|
-
options.success.apply(null, args);
|
|
17047
|
-
};
|
|
17048
|
-
|
|
17049
|
-
// Add the error handler.
|
|
17050
|
-
script.addEventListener("error", function () {
|
|
17051
|
-
cleanup();
|
|
17052
|
-
for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
|
|
17053
|
-
args[_key2] = arguments[_key2];
|
|
17054
|
-
}
|
|
17055
|
-
options.error.apply(null, args);
|
|
17056
|
-
});
|
|
17057
|
-
|
|
17058
|
-
// Insert the script to start the download.
|
|
17059
|
-
document.head?.appendChild(script);
|
|
17060
|
-
};
|
|
17061
|
-
|
|
17062
|
-
// For offline exercises in the mobile app, we download the graphie data
|
|
17063
|
-
// (svgs and localized data files) and serve them from the local file
|
|
17064
|
-
// system (with file://). We replace urls that start with `web+graphie`
|
|
17065
|
-
// in the perseus json with this `file+graphie` prefix to indicate that
|
|
17066
|
-
// they should have the `file://` protocol instead of `https://`.
|
|
17067
|
-
const svgLocalLabelsRegex = /^file\+graphie:/;
|
|
17068
|
-
const hashRegex = /\/([^/]+)$/;
|
|
17069
17165
|
function isImageProbablyPhotograph(imageUrl) {
|
|
17070
17166
|
// TODO(david): Do an inventory to refine this heuristic. For example, what
|
|
17071
17167
|
// % of .png images are illustrations?
|
|
17072
17168
|
return /\.(jpg|jpeg)$/i.test(imageUrl);
|
|
17073
17169
|
}
|
|
17074
|
-
function getLocale() {
|
|
17075
|
-
const {
|
|
17076
|
-
JIPT,
|
|
17077
|
-
kaLocale
|
|
17078
|
-
} = getDependencies();
|
|
17079
|
-
return JIPT.useJIPT ? "en-pt" : kaLocale;
|
|
17080
|
-
}
|
|
17081
|
-
function shouldUseLocalizedData() {
|
|
17082
|
-
return getLocale() !== "en";
|
|
17083
|
-
}
|
|
17084
|
-
|
|
17085
|
-
// A regex to split at the last / of a URL, separating the base part from the
|
|
17086
|
-
// hash. This is used to create the localized label data URLs.
|
|
17087
|
-
const splitHashRegex = /\/(?=[^/]+$)/;
|
|
17088
|
-
function getLocalizedDataUrl(url) {
|
|
17089
|
-
// For local (cached) graphie images, they are already localized.
|
|
17090
|
-
if (svgLocalLabelsRegex.test(url)) {
|
|
17091
|
-
return Util.getDataUrl(url);
|
|
17092
|
-
}
|
|
17093
|
-
const [base, hash] = Util.getBaseUrl(url).split(splitHashRegex);
|
|
17094
|
-
return `${base}/${getLocale()}/${hash}-data.json`;
|
|
17095
|
-
}
|
|
17096
|
-
|
|
17097
|
-
// Get the hash from the url, which is just the filename
|
|
17098
|
-
function getUrlHash(url) {
|
|
17099
|
-
const match = url.match(hashRegex);
|
|
17100
|
-
if (match == null) {
|
|
17101
|
-
throw new perseusCore.PerseusError("not a valid URL", perseusCore.Errors.InvalidInput);
|
|
17102
|
-
}
|
|
17103
|
-
return match && match[1];
|
|
17104
|
-
}
|
|
17105
17170
|
function defaultPreloader(dimensions) {
|
|
17106
17171
|
return /*#__PURE__*/React__namespace.createElement("span", {
|
|
17107
17172
|
style: {
|
|
@@ -17188,90 +17253,22 @@ class SvgImage extends React__namespace.Component {
|
|
|
17188
17253
|
return Util.isLabeledSVG(this.props.src) ? state.imageLoaded && state.dataLoaded : state.imageLoaded;
|
|
17189
17254
|
}
|
|
17190
17255
|
loadResources() {
|
|
17191
|
-
|
|
17192
|
-
|
|
17193
|
-
|
|
17194
|
-
|
|
17195
|
-
|
|
17196
|
-
if (labelDataCache[hash]) {
|
|
17197
|
-
if (labelDataCache[hash].loaded) {
|
|
17198
|
-
const {
|
|
17199
|
-
data,
|
|
17200
|
-
localized
|
|
17201
|
-
} = labelDataCache[hash];
|
|
17202
|
-
this.onDataLoaded(data, localized);
|
|
17203
|
-
} else {
|
|
17204
|
-
labelDataCache[hash].dataCallbacks.push(this.onDataLoaded);
|
|
17205
|
-
}
|
|
17206
|
-
} else {
|
|
17207
|
-
const cacheData = {
|
|
17208
|
-
loaded: false,
|
|
17209
|
-
dataCallbacks: [this.onDataLoaded],
|
|
17210
|
-
data: null,
|
|
17211
|
-
localized: shouldUseLocalizedData()
|
|
17212
|
-
};
|
|
17213
|
-
labelDataCache[hash] = cacheData;
|
|
17214
|
-
const retrieveData = (url, errorCallback) => {
|
|
17215
|
-
doJSONP(url, {
|
|
17216
|
-
callbackName: "svgData" + hash,
|
|
17217
|
-
success: data => {
|
|
17218
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'data' because it is a read-only property.
|
|
17219
|
-
cacheData.data = data;
|
|
17220
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'loaded' because it is a read-only property.
|
|
17221
|
-
cacheData.loaded = true;
|
|
17222
|
-
___default["default"].each(cacheData.dataCallbacks, callback => {
|
|
17223
|
-
// @ts-expect-error - TS2345 - Argument of type 'null' is not assignable to parameter of type '{ labels: readonly any[]; range: readonly any[]; }'.
|
|
17224
|
-
callback(cacheData.data, cacheData.localized);
|
|
17225
|
-
});
|
|
17226
|
-
},
|
|
17227
|
-
error: errorCallback
|
|
17228
|
-
});
|
|
17229
|
-
};
|
|
17230
|
-
if (shouldUseLocalizedData()) {
|
|
17231
|
-
retrieveData(getLocalizedDataUrl(this.props.src), (x, status, error) => {
|
|
17232
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'localized' because it is a read-only property.
|
|
17233
|
-
cacheData.localized = false;
|
|
17234
|
-
|
|
17235
|
-
// If there is isn't any localized data, fall back to
|
|
17236
|
-
// the original, unlocalized data
|
|
17237
|
-
retrieveData(Util.getDataUrl(this.props.src), (x, status, error) => {
|
|
17238
|
-
Log.error("Data load failed for svg-image", perseusCore.Errors.Service, {
|
|
17239
|
-
cause: error,
|
|
17240
|
-
loggedMetadata: {
|
|
17241
|
-
dataUrl: Util.getDataUrl(this.props.src),
|
|
17242
|
-
status
|
|
17243
|
-
}
|
|
17244
|
-
});
|
|
17245
|
-
});
|
|
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;
|
|
17246
17261
|
});
|
|
17247
|
-
|
|
17248
|
-
|
|
17249
|
-
|
|
17250
|
-
|
|
17251
|
-
|
|
17252
|
-
|
|
17253
|
-
status
|
|
17254
|
-
}
|
|
17255
|
-
});
|
|
17262
|
+
this.setState({
|
|
17263
|
+
dataLoaded: true,
|
|
17264
|
+
labelDataIsLocalized: localized,
|
|
17265
|
+
labelsRendered,
|
|
17266
|
+
labels: data.labels,
|
|
17267
|
+
range: data.range
|
|
17256
17268
|
});
|
|
17257
17269
|
}
|
|
17258
|
-
}
|
|
17270
|
+
});
|
|
17259
17271
|
}
|
|
17260
|
-
onDataLoaded = (data, localized) => {
|
|
17261
|
-
if (this._isMounted && data.labels && data.range) {
|
|
17262
|
-
const labelsRendered = data.labels.reduce((dict, label) => ({
|
|
17263
|
-
...dict,
|
|
17264
|
-
[label.content]: false
|
|
17265
|
-
}), {});
|
|
17266
|
-
this.setState({
|
|
17267
|
-
dataLoaded: true,
|
|
17268
|
-
labelDataIsLocalized: localized,
|
|
17269
|
-
labelsRendered,
|
|
17270
|
-
labels: data.labels,
|
|
17271
|
-
range: data.range
|
|
17272
|
-
});
|
|
17273
|
-
}
|
|
17274
|
-
};
|
|
17275
17272
|
sizeProvided() {
|
|
17276
17273
|
return this.props.width != null && this.props.height != null;
|
|
17277
17274
|
}
|
|
@@ -45530,7 +45527,7 @@ const init = function () {
|
|
|
45530
45527
|
|
|
45531
45528
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
45532
45529
|
const libName = "@khanacademy/perseus";
|
|
45533
|
-
const libVersion = "48.
|
|
45530
|
+
const libVersion = "48.1.0";
|
|
45534
45531
|
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
45535
45532
|
|
|
45536
45533
|
const apiVersion = {
|
|
@@ -49196,6 +49193,7 @@ exports.lockedFigureFillStyles = lockedFigureFillStyles;
|
|
|
49196
49193
|
exports.makeSafeUrl = makeSafeUrl;
|
|
49197
49194
|
exports.mathOnlyParser = mathOnlyParser;
|
|
49198
49195
|
exports.mediaQueries = mediaQueries;
|
|
49196
|
+
exports.parseDataFromJSONP = parseDataFromJSONP;
|
|
49199
49197
|
exports.parsePerseusItem = parsePerseusItem;
|
|
49200
49198
|
exports.plotterPlotTypes = plotterPlotTypes;
|
|
49201
49199
|
exports.preprocessTex = preprocessTex;
|