@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.
@@ -92,10 +92,6 @@ declare class SvgImage extends React.Component<Props, State> {
92
92
  componentWillUnmount(): void;
93
93
  isLoadedInState(state: State): boolean;
94
94
  loadResources(): void;
95
- onDataLoaded: (data: {
96
- labels: ReadonlyArray<any>;
97
- range: [Coord, Coord];
98
- }, localized: boolean) => void;
99
95
  sizeProvided(): boolean;
100
96
  onImageLoad: () => void;
101
97
  setupGraphie: (graphie?: any, options?: any) => void;
package/dist/es/index.js CHANGED
@@ -3373,6 +3373,162 @@ const Util = {
3373
3373
  deepClone
3374
3374
  };
3375
3375
 
3376
+ // For offline exercises in the mobile app, we download the graphie data
3377
+ // (svgs and localized data files) and serve them from the local file
3378
+ // system (with file://). We replace urls that start with `web+graphie`
3379
+ // in the perseus json with this `file+graphie` prefix to indicate that
3380
+ // they should have the `file://` protocol instead of `https://`.
3381
+ const svgLocalLabelsRegex = /^file\+graphie:/;
3382
+ const hashRegex = /\/([^/]+)$/;
3383
+
3384
+ /**
3385
+ * A Graphie axis range.
3386
+ */
3387
+
3388
+ /**
3389
+ * A Graphie label.
3390
+ */
3391
+
3392
+ /**
3393
+ * The Graphie data (labels, etc.)
3394
+ */
3395
+
3396
+ function getLocale() {
3397
+ const {
3398
+ JIPT,
3399
+ kaLocale
3400
+ } = getDependencies();
3401
+ return JIPT.useJIPT ? "en-pt" : kaLocale;
3402
+ }
3403
+ function shouldUseLocalizedData() {
3404
+ return getLocale() !== "en";
3405
+ }
3406
+
3407
+ // A regex to split at the last / of a URL, separating the base part from the
3408
+ // hash. This is used to create the localized label data URLs.
3409
+ const splitHashRegex = /\/(?=[^/]+$)/;
3410
+ function getLocalizedDataUrl(url) {
3411
+ // For local (cached) graphie images, they are already localized.
3412
+ if (svgLocalLabelsRegex.test(url)) {
3413
+ return Util.getDataUrl(url);
3414
+ }
3415
+ const [base, hash] = Util.getBaseUrl(url).split(splitHashRegex);
3416
+ return `${base}/${getLocale()}/${hash}-data.json`;
3417
+ }
3418
+
3419
+ // Get the hash from the url, which is just the filename
3420
+ function getUrlHash(url) {
3421
+ const match = url.match(hashRegex);
3422
+ if (match == null) {
3423
+ throw new PerseusError("not a valid URL", Errors.InvalidInput);
3424
+ }
3425
+ return match && match[1];
3426
+ }
3427
+
3428
+ /**
3429
+ * Parse JSONP for a web+graphie://... hash to extract Graphie data, or throw
3430
+ * an error if invalid JSONP.
3431
+ */
3432
+ function parseDataFromJSONP(graphieHash, graphieJSONP, errorCallback) {
3433
+ // The JSONP is expected to be in the form of `svgDataHASH(...)` or `svgOtherDataHASH(...)`
3434
+ const match = graphieJSONP.match(new RegExp(`^(?:svgData|svgOtherData)${graphieHash}\\((.+)\\);$`));
3435
+
3436
+ // It is also possible that the JSONP is simply a JSON object,
3437
+ // in which case we can parse it directly.
3438
+ const jsonToParse = match ? match[1] : graphieJSONP;
3439
+
3440
+ // Try to parse the JSONP, and if it fails, call the error callback
3441
+ try {
3442
+ return JSON.parse(jsonToParse);
3443
+ } catch (error) {
3444
+ errorCallback(error);
3445
+ return null;
3446
+ }
3447
+ }
3448
+ // The global cache of label data. Its format is:
3449
+ // {
3450
+ // hash (e.g. "c21435944d2cf0c8f39d9059cb35836aa701d04a"): {
3451
+ // loaded: a boolean of whether the data has been loaded or not
3452
+ // dataCallbacks: a list of callbacks to call with the data when the data
3453
+ // is loaded
3454
+ // data: the other data for this hash
3455
+ // },
3456
+ // ...
3457
+ // }
3458
+ const labelDataCache = {};
3459
+ function loadGraphie(url, onDataLoaded) {
3460
+ const hash = getUrlHash(url);
3461
+
3462
+ // We can't make multiple jsonp calls to the same file because their
3463
+ // callbacks will collide with each other. Instead, we cache the data
3464
+ // and only make the jsonp calls once.
3465
+ const entry = labelDataCache[hash];
3466
+ if (entry != null) {
3467
+ if (entry.loaded) {
3468
+ const {
3469
+ data,
3470
+ localized
3471
+ } = entry;
3472
+ onDataLoaded(data, localized);
3473
+ } else {
3474
+ entry.dataCallbacks.push(onDataLoaded);
3475
+ }
3476
+ } else {
3477
+ const cacheData = {
3478
+ loaded: false,
3479
+ dataCallbacks: [onDataLoaded],
3480
+ localized: shouldUseLocalizedData()
3481
+ };
3482
+ labelDataCache[hash] = cacheData;
3483
+ const retrieveData = async (url, errorCallback) => {
3484
+ const response = await fetch(url);
3485
+ if (!(response != null && response.ok)) {
3486
+ errorCallback();
3487
+ return;
3488
+ }
3489
+ const jsonp = await response.text();
3490
+ const data = parseDataFromJSONP(hash, jsonp, error => {
3491
+ Log.error("Failed to parse JSONP for svg-image", Errors.Service, {
3492
+ cause: error,
3493
+ loggedMetadata: {
3494
+ dataUrl: Util.getDataUrl(url),
3495
+ jsonp
3496
+ }
3497
+ });
3498
+ });
3499
+
3500
+ // If the data is null, then we failed to parse the JSONP
3501
+ if (!data) {
3502
+ return;
3503
+ }
3504
+ const newCacheEntry = labelDataCache[hash] = _extends({}, labelDataCache[hash], {
3505
+ loaded: true,
3506
+ data
3507
+ });
3508
+ newCacheEntry.dataCallbacks.forEach(callback => {
3509
+ callback(newCacheEntry.data, cacheData.localized);
3510
+ });
3511
+ };
3512
+ const dataLoadErrorHandler = (x, status, error) => {
3513
+ Log.error("Data load failed for svg-image", Errors.Service, {
3514
+ cause: error,
3515
+ loggedMetadata: {
3516
+ dataUrl: Util.getDataUrl(url),
3517
+ status
3518
+ }
3519
+ });
3520
+ };
3521
+ if (shouldUseLocalizedData()) {
3522
+ cacheData.localized = false;
3523
+ // If there is isn't any localized data, fall back to
3524
+ // the original, unlocalized data
3525
+ retrieveData(getLocalizedDataUrl(url), dataLoadErrorHandler);
3526
+ } else {
3527
+ retrieveData(Util.getDataUrl(url), dataLoadErrorHandler);
3528
+ }
3529
+ }
3530
+ }
3531
+
3376
3532
  // Derived from the MIT-licensed:
3377
3533
 
3378
3534
  /*jshint browser:true, node:true */
@@ -8959,6 +9115,9 @@ class Graphie$1 {
8959
9115
  $(el).css("position", "relative");
8960
9116
  this.raphael = Raphael$1(el);
8961
9117
 
9118
+ // Hide the Raphael canvas from screen readers
9119
+ $(el).attr("aria-hidden", "true");
9120
+
8962
9121
  // For a sometimes-reproducible IE8 bug; doesn't affect SVG browsers at all
8963
9122
  $(el).children("div").css("position", "absolute");
8964
9123
  }
@@ -16832,101 +16991,11 @@ class ImageLoader extends React.Component {
16832
16991
 
16833
16992
  // Minimum image width to make an image appear as zoomable.
16834
16993
  const ZOOMABLE_THRESHOLD = 700;
16835
-
16836
- // The global cache of label data. Its format is:
16837
- // {
16838
- // hash (e.g. "c21435944d2cf0c8f39d9059cb35836aa701d04a"): {
16839
- // loaded: a boolean of whether the data has been loaded or not
16840
- // dataCallbacks: a list of callbacks to call with the data when the data
16841
- // is loaded
16842
- // data: the other data for this hash
16843
- // },
16844
- // ...
16845
- // }
16846
- const labelDataCache = {};
16847
-
16848
- // Write our own JSONP handler because all the other ones don't do things we
16849
- // need.
16850
- const doJSONP = function doJSONP(url, options) {
16851
- var _document$head2;
16852
- options = _extends({
16853
- callbackName: "callback",
16854
- success: $.noop,
16855
- error: $.noop
16856
- }, options);
16857
-
16858
- // Create the script
16859
- const script = document.createElement("script");
16860
- script.setAttribute("async", "");
16861
- script.setAttribute("src", url);
16862
-
16863
- // A cleanup function to run when we're done.
16864
- function cleanup() {
16865
- var _document$head;
16866
- (_document$head = document.head) == null || _document$head.removeChild(script);
16867
- delete window[options.callbackName];
16868
- }
16869
-
16870
- // Add the global callback.
16871
- // @ts-expect-error - TS2740 - Type '() => void' is missing the following properties from type 'Window': clientInformation, closed, customElements, devicePixelRatio, and 206 more.
16872
- window[options.callbackName] = function (...args) {
16873
- cleanup();
16874
- options.success.apply(null, args);
16875
- };
16876
-
16877
- // Add the error handler.
16878
- script.addEventListener("error", function (...args) {
16879
- cleanup();
16880
- options.error.apply(null, args);
16881
- });
16882
-
16883
- // Insert the script to start the download.
16884
- (_document$head2 = document.head) == null || _document$head2.appendChild(script);
16885
- };
16886
-
16887
- // For offline exercises in the mobile app, we download the graphie data
16888
- // (svgs and localized data files) and serve them from the local file
16889
- // system (with file://). We replace urls that start with `web+graphie`
16890
- // in the perseus json with this `file+graphie` prefix to indicate that
16891
- // they should have the `file://` protocol instead of `https://`.
16892
- const svgLocalLabelsRegex = /^file\+graphie:/;
16893
- const hashRegex = /\/([^/]+)$/;
16894
16994
  function isImageProbablyPhotograph(imageUrl) {
16895
16995
  // TODO(david): Do an inventory to refine this heuristic. For example, what
16896
16996
  // % of .png images are illustrations?
16897
16997
  return /\.(jpg|jpeg)$/i.test(imageUrl);
16898
16998
  }
16899
- function getLocale() {
16900
- const {
16901
- JIPT,
16902
- kaLocale
16903
- } = getDependencies();
16904
- return JIPT.useJIPT ? "en-pt" : kaLocale;
16905
- }
16906
- function shouldUseLocalizedData() {
16907
- return getLocale() !== "en";
16908
- }
16909
-
16910
- // A regex to split at the last / of a URL, separating the base part from the
16911
- // hash. This is used to create the localized label data URLs.
16912
- const splitHashRegex = /\/(?=[^/]+$)/;
16913
- function getLocalizedDataUrl(url) {
16914
- // For local (cached) graphie images, they are already localized.
16915
- if (svgLocalLabelsRegex.test(url)) {
16916
- return Util.getDataUrl(url);
16917
- }
16918
- const [base, hash] = Util.getBaseUrl(url).split(splitHashRegex);
16919
- return `${base}/${getLocale()}/${hash}-data.json`;
16920
- }
16921
-
16922
- // Get the hash from the url, which is just the filename
16923
- function getUrlHash(url) {
16924
- const match = url.match(hashRegex);
16925
- if (match == null) {
16926
- throw new PerseusError("not a valid URL", Errors.InvalidInput);
16927
- }
16928
- return match && match[1];
16929
- }
16930
16999
  function defaultPreloader(dimensions) {
16931
17000
  return /*#__PURE__*/React.createElement("span", {
16932
17001
  style: {
@@ -16948,20 +17017,6 @@ class SvgImage extends React.Component {
16948
17017
  constructor(props) {
16949
17018
  super(props);
16950
17019
  this._isMounted = void 0;
16951
- this.onDataLoaded = (data, localized) => {
16952
- if (this._isMounted && data.labels && data.range) {
16953
- const labelsRendered = data.labels.reduce((dict, label) => _extends({}, dict, {
16954
- [label.content]: false
16955
- }), {});
16956
- this.setState({
16957
- dataLoaded: true,
16958
- labelDataIsLocalized: localized,
16959
- labelsRendered,
16960
- labels: data.labels,
16961
- range: data.range
16962
- });
16963
- }
16964
- };
16965
17020
  this.onImageLoad = () => {
16966
17021
  // Only need to do this if rendering a Graphie
16967
17022
  if (this.sizeProvided()) {
@@ -17137,74 +17192,21 @@ class SvgImage extends React.Component {
17137
17192
  return Util.isLabeledSVG(this.props.src) ? state.imageLoaded && state.dataLoaded : state.imageLoaded;
17138
17193
  }
17139
17194
  loadResources() {
17140
- const hash = getUrlHash(this.props.src);
17141
-
17142
- // We can't make multiple jsonp calls to the same file because their
17143
- // callbacks will collide with each other. Instead, we cache the data
17144
- // and only make the jsonp calls once.
17145
- if (labelDataCache[hash]) {
17146
- if (labelDataCache[hash].loaded) {
17147
- const {
17148
- data,
17149
- localized
17150
- } = labelDataCache[hash];
17151
- this.onDataLoaded(data, localized);
17152
- } else {
17153
- labelDataCache[hash].dataCallbacks.push(this.onDataLoaded);
17154
- }
17155
- } else {
17156
- const cacheData = {
17157
- loaded: false,
17158
- dataCallbacks: [this.onDataLoaded],
17159
- data: null,
17160
- localized: shouldUseLocalizedData()
17161
- };
17162
- labelDataCache[hash] = cacheData;
17163
- const retrieveData = (url, errorCallback) => {
17164
- doJSONP(url, {
17165
- callbackName: "svgData" + hash,
17166
- success: data => {
17167
- // @ts-expect-error - TS2540 - Cannot assign to 'data' because it is a read-only property.
17168
- cacheData.data = data;
17169
- // @ts-expect-error - TS2540 - Cannot assign to 'loaded' because it is a read-only property.
17170
- cacheData.loaded = true;
17171
- _.each(cacheData.dataCallbacks, callback => {
17172
- // @ts-expect-error - TS2345 - Argument of type 'null' is not assignable to parameter of type '{ labels: readonly any[]; range: readonly any[]; }'.
17173
- callback(cacheData.data, cacheData.localized);
17174
- });
17175
- },
17176
- error: errorCallback
17177
- });
17178
- };
17179
- if (shouldUseLocalizedData()) {
17180
- retrieveData(getLocalizedDataUrl(this.props.src), (x, status, error) => {
17181
- // @ts-expect-error - TS2540 - Cannot assign to 'localized' because it is a read-only property.
17182
- cacheData.localized = false;
17183
-
17184
- // If there is isn't any localized data, fall back to
17185
- // the original, unlocalized data
17186
- retrieveData(Util.getDataUrl(this.props.src), (x, status, error) => {
17187
- Log.error("Data load failed for svg-image", Errors.Service, {
17188
- cause: error,
17189
- loggedMetadata: {
17190
- dataUrl: Util.getDataUrl(this.props.src),
17191
- status
17192
- }
17193
- });
17194
- });
17195
+ loadGraphie(this.props.src, (data, localized) => {
17196
+ if (this._isMounted && data.labels && data.range) {
17197
+ const labelsRendered = {};
17198
+ data.labels.forEach(label => {
17199
+ labelsRendered[label.content] = false;
17195
17200
  });
17196
- } else {
17197
- retrieveData(Util.getDataUrl(this.props.src), (x, status, error) => {
17198
- Log.error("Data load failed for svg-image", Errors.Service, {
17199
- cause: error,
17200
- loggedMetadata: {
17201
- dataUrl: Util.getDataUrl(this.props.src),
17202
- status
17203
- }
17204
- });
17201
+ this.setState({
17202
+ dataLoaded: true,
17203
+ labelDataIsLocalized: localized,
17204
+ labelsRendered,
17205
+ labels: data.labels,
17206
+ range: data.range
17205
17207
  });
17206
17208
  }
17207
- }
17209
+ });
17208
17210
  }
17209
17211
  sizeProvided() {
17210
17212
  return this.props.width != null && this.props.height != null;
@@ -45170,7 +45172,7 @@ const init = function init() {
45170
45172
 
45171
45173
  // This file is processed by a Rollup plugin (replace) to inject the production
45172
45174
  const libName = "@khanacademy/perseus";
45173
- const libVersion = "48.0.0";
45175
+ const libVersion = "48.1.0";
45174
45176
  addLibraryVersionToPerseusDebug(libName, libVersion);
45175
45177
 
45176
45178
  const apiVersion = {
@@ -48722,5 +48724,5 @@ const ScoringUtil = {
48722
48724
  * Types
48723
48725
  */
48724
48726
 
48725
- export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, NumericInput$1 as NumericInput, PerseusExpressionAnswerFormConsidered, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ScoringUtil, ref as ServerItemRenderer, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, addWidget, apiVersion, bodyXsmallBold, components, containerSizeClass, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getClockwiseAngle, getCorrectAnswerForWidgetId, getImagesWithoutAltData, getInteractiveBoxFromSizeClass, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, iconChevronDown, iconTrash, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, ItemVersion as itemVersion, libVersion, lockedFigureColors, lockedFigureFillStyles, makeSafeUrl, mathOnlyParser, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, scorePerseusItem, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
48727
+ export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, NumericInput$1 as NumericInput, PerseusExpressionAnswerFormConsidered, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ScoringUtil, ref as ServerItemRenderer, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, addWidget, apiVersion, bodyXsmallBold, components, containerSizeClass, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getClockwiseAngle, getCorrectAnswerForWidgetId, getImagesWithoutAltData, getInteractiveBoxFromSizeClass, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, iconChevronDown, iconTrash, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, ItemVersion as itemVersion, libVersion, lockedFigureColors, lockedFigureFillStyles, makeSafeUrl, mathOnlyParser, mediaQueries, parseDataFromJSONP, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, scorePerseusItem, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
48726
48728
  //# sourceMappingURL=index.js.map