@khanacademy/perseus 23.6.0 → 24.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/es/index.css +29 -1
- package/dist/es/index.js +401 -45
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +29 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +409 -50
- package/dist/index.js.map +1 -1
- package/dist/util/svg.d.ts +5 -0
- package/dist/widgets/interactive-graph.d.ts +2 -1
- package/dist/widgets/interactive-graphs/graphs/use-draggable.d.ts +20 -3
- package/dist/widgets/interactive-graphs/interactive-graph-question-builder.d.ts +12 -3
- package/dist/widgets/interactive-graphs/mafs-graph.d.ts +2 -0
- package/dist/widgets/interactive-graphs/protractor.d.ts +5 -0
- package/dist/widgets/interactive-graphs/utils.d.ts +7 -1
- package/package.json +2 -1
package/dist/index.css
CHANGED
|
@@ -3518,7 +3518,7 @@ body.mobile .framework-perseus table.perseus-widget-table-of-values.non-markdown
|
|
|
3518
3518
|
font-size: 18px;
|
|
3519
3519
|
}
|
|
3520
3520
|
}
|
|
3521
|
-
.perseus-widget-editor-content .
|
|
3521
|
+
.perseus-widget-editor-content .perseus-editor-accordion h2 {
|
|
3522
3522
|
padding-top: 0;
|
|
3523
3523
|
}
|
|
3524
3524
|
.keypad-input {
|
|
@@ -4143,6 +4143,34 @@ html body .mq-editable-field .mq-selection .mq-nthroot-container * {
|
|
|
4143
4143
|
}
|
|
4144
4144
|
|
|
4145
4145
|
|
|
4146
|
+
.MafsView .protractor-rotation-handle {
|
|
4147
|
+
cursor: grab;
|
|
4148
|
+
/* `pointer-events: all` is needed to make the hit target the size of the
|
|
4149
|
+
* entire <g> element and not just its visible subelements */
|
|
4150
|
+
pointer-events: all;
|
|
4151
|
+
transition: transform 0.1s ease-out;
|
|
4152
|
+
}
|
|
4153
|
+
|
|
4154
|
+
.MafsView .protractor-rotation-handle:hover {
|
|
4155
|
+
transform: scale(1.3);
|
|
4156
|
+
}
|
|
4157
|
+
|
|
4158
|
+
.MafsView .protractor-rotation-handle-arrow-arc {
|
|
4159
|
+
fill: none;
|
|
4160
|
+
stroke: var(--mafs-blue);
|
|
4161
|
+
stroke-width: 8;
|
|
4162
|
+
/* `strokeLinecap: square` prevents a hairline crack from appearing
|
|
4163
|
+
* between the arc and the arrowheads, by making the arc extend a bit
|
|
4164
|
+
* beyond its start and end coordinates.
|
|
4165
|
+
*/
|
|
4166
|
+
stroke-linecap: square;
|
|
4167
|
+
}
|
|
4168
|
+
|
|
4169
|
+
.MafsView .protractor-rotation-handle-arrowhead {
|
|
4170
|
+
fill: var(--mafs-blue);
|
|
4171
|
+
stroke: none;
|
|
4172
|
+
}
|
|
4173
|
+
|
|
4146
4174
|
.MafsView {
|
|
4147
4175
|
display: block;
|
|
4148
4176
|
background: var(--mafs-bg);
|
package/dist/index.d.ts
CHANGED
|
@@ -87,7 +87,7 @@ export type { ILogger, LogErrorOptions, ErrorKind } from "./logging/log";
|
|
|
87
87
|
export type { ServerItemRenderer as ServerItemRendererComponent } from "./server-item-renderer";
|
|
88
88
|
export type { Alignment, APIOptions, APIOptionsWithDefaults, ChangeHandler, DeviceType, DomInsertCheckFn, EditorMode, FocusPath, Hint, ImageDict, ImageUploader, JiptLabelStore, JiptRenderer, PerseusDependencies, PerseusScore, Version, VideoData, VideoKind, WidgetDict, WidgetExports, } from "./types";
|
|
89
89
|
export type { ParsedValue } from "./util";
|
|
90
|
-
export type { LockedFigure, LockedFigureColor, LockedFigureFillType, LockedFigureType, LockedPointType, LockedLineType, LockedVectorType, LockedEllipseType, LockedPolygonType, PerseusGraphType, PerseusAnswerArea, PerseusExpressionWidgetOptions, Range, Size, MathFormat, InputNumberWidget, // TODO(jeremy): remove?
|
|
90
|
+
export type { LockedFigure, LockedFigureColor, LockedFigureFillType, LockedFigureType, LockedPointType, LockedLineType, LockedVectorType, LockedEllipseType, LockedPolygonType, PerseusGraphType, PerseusAnswerArea, PerseusExpressionWidgetOptions, Range, Size, CollinearTuple, MathFormat, InputNumberWidget, // TODO(jeremy): remove?
|
|
91
91
|
PerseusImageBackground, PerseusInputNumberWidgetOptions, PerseusInteractiveGraphWidgetOptions, PerseusItem, PerseusPlotterWidgetOptions, PerseusPythonProgramWidgetOptions, PerseusRadioWidgetOptions, PerseusExampleWidgetOptions, PerseusExampleGraphieWidgetOptions, PerseusSimpleMarkdownTesterWidgetOptions, PerseusRenderer, PerseusWidget, PerseusWidgetsMap, } from "./perseus-types";
|
|
92
92
|
export type { Coord } from "./interactive2/types";
|
|
93
93
|
export type { MarkerType } from "./widgets/label-image/types";
|
package/dist/index.js
CHANGED
|
@@ -31,6 +31,7 @@ var caretDown = require('@phosphor-icons/core/regular/caret-down.svg');
|
|
|
31
31
|
var caretUp = require('@phosphor-icons/core/regular/caret-up.svg');
|
|
32
32
|
var debounce$1 = require('lodash.debounce');
|
|
33
33
|
var mafs = require('mafs');
|
|
34
|
+
var react = require('@use-gesture/react');
|
|
34
35
|
var wonderBlocksIcon = require('@khanacademy/wonder-blocks-icon');
|
|
35
36
|
var wonderBlocksTypography = require('@khanacademy/wonder-blocks-typography');
|
|
36
37
|
var questionIcon = require('@phosphor-icons/core/regular/question.svg');
|
|
@@ -634,8 +635,7 @@ class InnerMathInput extends React__namespace.Component {
|
|
|
634
635
|
active: false
|
|
635
636
|
}) : /*#__PURE__*/React__namespace.createElement(Clickable__default["default"], {
|
|
636
637
|
"aria-label": this.state.keypadOpen ? this.context.strings.closeKeypad : this.context.strings.openKeypad,
|
|
637
|
-
"
|
|
638
|
-
role: "switch",
|
|
638
|
+
role: "button",
|
|
639
639
|
onClick: () => this.state.keypadOpen ? this.closeKeypad() : this.openKeypad()
|
|
640
640
|
}, props => /*#__PURE__*/React__namespace.createElement(MathInputIcon, _extends({
|
|
641
641
|
active: this.state.keypadOpen
|
|
@@ -13188,7 +13188,7 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
|
|
|
13188
13188
|
}(),
|
|
13189
13189
|
protractor: function (center) {
|
|
13190
13190
|
// @ts-expect-error - TS2350 - Only a void function can be called with the 'new' keyword.
|
|
13191
|
-
return new Protractor(this, center);
|
|
13191
|
+
return new Protractor$1(this, center);
|
|
13192
13192
|
},
|
|
13193
13193
|
ruler: function (options) {
|
|
13194
13194
|
// @ts-expect-error - TS2350 - Only a void function can be called with the 'new' keyword.
|
|
@@ -13196,7 +13196,7 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
|
|
|
13196
13196
|
},
|
|
13197
13197
|
addPoints: kmath.vector.add
|
|
13198
13198
|
});
|
|
13199
|
-
function Protractor(graph, center) {
|
|
13199
|
+
function Protractor$1(graph, center) {
|
|
13200
13200
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
13201
13201
|
this.set = graph.raphael.set();
|
|
13202
13202
|
|
|
@@ -28850,7 +28850,25 @@ class PathBuilder {
|
|
|
28850
28850
|
move(x, y) {
|
|
28851
28851
|
this.path.push({
|
|
28852
28852
|
action: "M",
|
|
28853
|
-
|
|
28853
|
+
args: [x, y]
|
|
28854
|
+
});
|
|
28855
|
+
return this;
|
|
28856
|
+
}
|
|
28857
|
+
line(x, y) {
|
|
28858
|
+
this.path.push({
|
|
28859
|
+
action: "L",
|
|
28860
|
+
args: [x, y]
|
|
28861
|
+
});
|
|
28862
|
+
return this;
|
|
28863
|
+
}
|
|
28864
|
+
circularArc(radius, toX, toY) {
|
|
28865
|
+
let {
|
|
28866
|
+
sweep = false,
|
|
28867
|
+
largeArc = false
|
|
28868
|
+
} = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
|
|
28869
|
+
this.path.push({
|
|
28870
|
+
action: "A",
|
|
28871
|
+
args: [radius, radius, 0 /* rotation */, largeArc ? 1 : 0, sweep ? 1 : 0, toX, toY]
|
|
28854
28872
|
});
|
|
28855
28873
|
return this;
|
|
28856
28874
|
}
|
|
@@ -28859,7 +28877,7 @@ class PathBuilder {
|
|
|
28859
28877
|
curve(control1X, control1Y, control2X, control2Y, endX, endY) {
|
|
28860
28878
|
this.path.push({
|
|
28861
28879
|
action: "C",
|
|
28862
|
-
|
|
28880
|
+
args: [control1X, control1Y, control2X, control2Y, endX, endY]
|
|
28863
28881
|
});
|
|
28864
28882
|
return this;
|
|
28865
28883
|
}
|
|
@@ -28871,13 +28889,40 @@ class PathBuilder {
|
|
|
28871
28889
|
}
|
|
28872
28890
|
}
|
|
28873
28891
|
function commandToString(command) {
|
|
28874
|
-
return `${command.action}${command.
|
|
28892
|
+
return `${command.action}${command.args.join(" ")}`;
|
|
28875
28893
|
}
|
|
28876
28894
|
function scaleCommandBy(scaleFactor) {
|
|
28877
|
-
return command =>
|
|
28878
|
-
|
|
28879
|
-
|
|
28880
|
-
|
|
28895
|
+
return command => {
|
|
28896
|
+
switch (command.action) {
|
|
28897
|
+
case "A":
|
|
28898
|
+
// TODO(benchristel): do we want to refactor this switch to
|
|
28899
|
+
// use polymorphism?
|
|
28900
|
+
// Arc command
|
|
28901
|
+
return {
|
|
28902
|
+
...command,
|
|
28903
|
+
args: [command.args[0] * scaleFactor,
|
|
28904
|
+
// x radius
|
|
28905
|
+
command.args[1] * scaleFactor,
|
|
28906
|
+
// y radius
|
|
28907
|
+
command.args[2],
|
|
28908
|
+
// rotation
|
|
28909
|
+
command.args[3],
|
|
28910
|
+
// largeArc flag
|
|
28911
|
+
command.args[4],
|
|
28912
|
+
// sweep flag
|
|
28913
|
+
command.args[5] * scaleFactor,
|
|
28914
|
+
// end x
|
|
28915
|
+
command.args[6] * scaleFactor // end y
|
|
28916
|
+
]
|
|
28917
|
+
};
|
|
28918
|
+
|
|
28919
|
+
default:
|
|
28920
|
+
return {
|
|
28921
|
+
...command,
|
|
28922
|
+
args: command.args.map(c => c * scaleFactor)
|
|
28923
|
+
};
|
|
28924
|
+
}
|
|
28925
|
+
};
|
|
28881
28926
|
}
|
|
28882
28927
|
|
|
28883
28928
|
// We use the pathBuilder here to scale up the SVG path coordinates used
|
|
@@ -29265,8 +29310,171 @@ function snap(snapStep, point) {
|
|
|
29265
29310
|
return [Math.round(requestedX / snapX) * snapX, Math.round(requestedY / snapY) * snapY];
|
|
29266
29311
|
}
|
|
29267
29312
|
|
|
29268
|
-
|
|
29269
|
-
|
|
29313
|
+
// Returns the closest point to the given `point` that is within the graph
|
|
29314
|
+
// bounds given in `state`.
|
|
29315
|
+
function bound$1(_ref) {
|
|
29316
|
+
let {
|
|
29317
|
+
snapStep,
|
|
29318
|
+
range,
|
|
29319
|
+
point
|
|
29320
|
+
} = _ref;
|
|
29321
|
+
const [requestedX, requestedY] = point;
|
|
29322
|
+
const [snapX, snapY] = snapStep;
|
|
29323
|
+
const [[minX, maxX], [minY, maxY]] = range;
|
|
29324
|
+
return [clamp(requestedX, minX + snapX, maxX - snapX), clamp(requestedY, minY + snapY, maxY - snapY)];
|
|
29325
|
+
}
|
|
29326
|
+
function clamp(value, min, max) {
|
|
29327
|
+
if (value < min) {
|
|
29328
|
+
return min;
|
|
29329
|
+
}
|
|
29330
|
+
if (value > max) {
|
|
29331
|
+
return max;
|
|
29332
|
+
}
|
|
29333
|
+
return value;
|
|
29334
|
+
}
|
|
29335
|
+
|
|
29336
|
+
var isProduction = process.env.NODE_ENV === 'production';
|
|
29337
|
+
var prefix = 'Invariant failed';
|
|
29338
|
+
function invariant(condition, message) {
|
|
29339
|
+
if (condition) {
|
|
29340
|
+
return;
|
|
29341
|
+
}
|
|
29342
|
+
if (isProduction) {
|
|
29343
|
+
throw new Error(prefix);
|
|
29344
|
+
}
|
|
29345
|
+
var provided = typeof message === 'function' ? message() : message;
|
|
29346
|
+
var value = provided ? "".concat(prefix, ": ").concat(provided) : prefix;
|
|
29347
|
+
throw new Error(value);
|
|
29348
|
+
}
|
|
29349
|
+
|
|
29350
|
+
/**
|
|
29351
|
+
* Code in this file is derived from
|
|
29352
|
+
* https://github.com/stevenpetryk/mafs/blob/4520319379a2cc2df8148d8baaef1f85db117103/src/interaction/useMovable.tsx#L20-L83
|
|
29353
|
+
* and copied here under the terms of the MIT license.
|
|
29354
|
+
*
|
|
29355
|
+
* Copyright 2021 Steven Petryk
|
|
29356
|
+
*
|
|
29357
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
29358
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
29359
|
+
* in the Software without restriction, including without limitation the rights
|
|
29360
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
29361
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
29362
|
+
* furnished to do so, subject to the following conditions:
|
|
29363
|
+
*
|
|
29364
|
+
* The above copyright notice and this permission notice shall be included in
|
|
29365
|
+
* all copies or substantial portions of the Software.
|
|
29366
|
+
*/
|
|
29367
|
+
|
|
29368
|
+
function useDraggable(args) {
|
|
29369
|
+
const {
|
|
29370
|
+
gestureTarget: target,
|
|
29371
|
+
onMove,
|
|
29372
|
+
point,
|
|
29373
|
+
constrain
|
|
29374
|
+
} = args;
|
|
29375
|
+
const [dragging, setDragging] = React__namespace.useState(false);
|
|
29376
|
+
const {
|
|
29377
|
+
xSpan,
|
|
29378
|
+
ySpan
|
|
29379
|
+
} = useSpanContext();
|
|
29380
|
+
const {
|
|
29381
|
+
viewTransform,
|
|
29382
|
+
userTransform
|
|
29383
|
+
} = mafs.useTransformContext();
|
|
29384
|
+
const inverseViewTransform = mafs.vec.matrixInvert(viewTransform);
|
|
29385
|
+
invariant(inverseViewTransform, "The view transform must be invertible.");
|
|
29386
|
+
const inverseTransform = React__namespace.useMemo(() => getInverseTransform(userTransform), [userTransform]);
|
|
29387
|
+
const pickup = React__namespace.useRef([0, 0]);
|
|
29388
|
+
react.useDrag(state => {
|
|
29389
|
+
const {
|
|
29390
|
+
type,
|
|
29391
|
+
event
|
|
29392
|
+
} = state;
|
|
29393
|
+
event?.stopPropagation();
|
|
29394
|
+
const isKeyboard = type.includes("key");
|
|
29395
|
+
if (isKeyboard) {
|
|
29396
|
+
event?.preventDefault();
|
|
29397
|
+
const {
|
|
29398
|
+
direction: yDownDirection,
|
|
29399
|
+
altKey,
|
|
29400
|
+
metaKey,
|
|
29401
|
+
shiftKey
|
|
29402
|
+
} = state;
|
|
29403
|
+
const direction = [yDownDirection[0], -yDownDirection[1]];
|
|
29404
|
+
const span = Math.abs(direction[0]) ? xSpan : ySpan;
|
|
29405
|
+
let divisions = 50;
|
|
29406
|
+
if (altKey || metaKey) {
|
|
29407
|
+
divisions = 200;
|
|
29408
|
+
}
|
|
29409
|
+
if (shiftKey) {
|
|
29410
|
+
divisions = 10;
|
|
29411
|
+
}
|
|
29412
|
+
const min = span / (divisions * 2);
|
|
29413
|
+
const tests = range(span / divisions, span / 2, span / divisions);
|
|
29414
|
+
for (const dx of tests) {
|
|
29415
|
+
// Transform the test back into the point's coordinate system
|
|
29416
|
+
const testMovement = mafs.vec.scale(direction, dx);
|
|
29417
|
+
const testPoint = constrain(mafs.vec.transform(mafs.vec.add(mafs.vec.transform(point, userTransform), testMovement), inverseTransform));
|
|
29418
|
+
if (mafs.vec.dist(testPoint, point) > min) {
|
|
29419
|
+
onMove(testPoint);
|
|
29420
|
+
break;
|
|
29421
|
+
}
|
|
29422
|
+
}
|
|
29423
|
+
} else {
|
|
29424
|
+
const {
|
|
29425
|
+
last,
|
|
29426
|
+
movement: pixelMovement,
|
|
29427
|
+
first
|
|
29428
|
+
} = state;
|
|
29429
|
+
setDragging(!last);
|
|
29430
|
+
if (first) {
|
|
29431
|
+
pickup.current = mafs.vec.transform(point, userTransform);
|
|
29432
|
+
}
|
|
29433
|
+
if (mafs.vec.mag(pixelMovement) === 0) {
|
|
29434
|
+
return;
|
|
29435
|
+
}
|
|
29436
|
+
const movement = mafs.vec.transform(pixelMovement, inverseViewTransform);
|
|
29437
|
+
onMove(constrain(mafs.vec.transform(mafs.vec.add(pickup.current, movement), inverseTransform)));
|
|
29438
|
+
}
|
|
29439
|
+
}, {
|
|
29440
|
+
target,
|
|
29441
|
+
eventOptions: {
|
|
29442
|
+
passive: false
|
|
29443
|
+
}
|
|
29444
|
+
});
|
|
29445
|
+
return {
|
|
29446
|
+
dragging
|
|
29447
|
+
};
|
|
29448
|
+
}
|
|
29449
|
+
function getInverseTransform(transform) {
|
|
29450
|
+
const invert = mafs.vec.matrixInvert(transform);
|
|
29451
|
+
invariant(invert !== null, "Could not invert transform matrix. A parent transformation matrix might be degenerative (mapping 2D space to a line).");
|
|
29452
|
+
return invert;
|
|
29453
|
+
}
|
|
29454
|
+
function useSpanContext() {
|
|
29455
|
+
const {
|
|
29456
|
+
range: [[xMin, xMax], [yMin, yMax]]
|
|
29457
|
+
} = useGraphConfig();
|
|
29458
|
+
const xSpan = xMax - xMin;
|
|
29459
|
+
const ySpan = yMax - yMin;
|
|
29460
|
+
return {
|
|
29461
|
+
xSpan,
|
|
29462
|
+
ySpan
|
|
29463
|
+
};
|
|
29464
|
+
}
|
|
29465
|
+
function range(min, max) {
|
|
29466
|
+
let step = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
|
|
29467
|
+
const result = [];
|
|
29468
|
+
for (let i = min; i < max - step / 2; i += step) {
|
|
29469
|
+
result.push(i);
|
|
29470
|
+
}
|
|
29471
|
+
const computedMax = result[result.length - 1] + step;
|
|
29472
|
+
if (Math.abs(max - computedMax) < step / 1e-6) {
|
|
29473
|
+
result.push(max);
|
|
29474
|
+
} else {
|
|
29475
|
+
result.push(computedMax);
|
|
29476
|
+
}
|
|
29477
|
+
return result;
|
|
29270
29478
|
}
|
|
29271
29479
|
|
|
29272
29480
|
// The hitbox size of 48px by 48px is preserved from the legacy interactive
|
|
@@ -31317,6 +31525,154 @@ const LegacyGrid = _ref => {
|
|
|
31317
31525
|
return null;
|
|
31318
31526
|
};
|
|
31319
31527
|
|
|
31528
|
+
const protractorImage = "https://ka-perseus-graphie.s3.amazonaws.com/e9d032f2ab8b95979f674fbfa67056442ba1ff6a.png";
|
|
31529
|
+
|
|
31530
|
+
// The vector from the center of the protractor to the top left corner of the
|
|
31531
|
+
// protractor image, in pixels. Used for positioning.
|
|
31532
|
+
const centerToTopLeft = [-180, -170];
|
|
31533
|
+
|
|
31534
|
+
// The vector from the center of the protractor to the center of the rotation
|
|
31535
|
+
// handle.
|
|
31536
|
+
const centerToRotationHandle = [-176, -15];
|
|
31537
|
+
function Protractor() {
|
|
31538
|
+
const {
|
|
31539
|
+
range,
|
|
31540
|
+
snapStep
|
|
31541
|
+
} = useGraphConfig();
|
|
31542
|
+
const [[xMin, xMax], [yMin, yMax]] = range;
|
|
31543
|
+
// Position the protractor in the center of the graph (horizontally), and
|
|
31544
|
+
// 95% of the way to the bottom of the graph (vertically).
|
|
31545
|
+
const initialCenter = [lerp(xMin, xMax, 0.5), lerp(yMin, yMax, 0.05)];
|
|
31546
|
+
const [center, setCenter] = React.useState(initialCenter);
|
|
31547
|
+
const [rotationHandleOffset, setRotationHandleOffset] = React.useState(centerToRotationHandle);
|
|
31548
|
+
const draggableRef = React.useRef(null);
|
|
31549
|
+
useDraggable({
|
|
31550
|
+
gestureTarget: draggableRef,
|
|
31551
|
+
onMove: setCenter,
|
|
31552
|
+
point: center,
|
|
31553
|
+
constrain: point => bound$1({
|
|
31554
|
+
snapStep,
|
|
31555
|
+
range,
|
|
31556
|
+
point
|
|
31557
|
+
})
|
|
31558
|
+
});
|
|
31559
|
+
const rotationHandleRef = React.useRef(null);
|
|
31560
|
+
useDraggablePx({
|
|
31561
|
+
gestureTarget: rotationHandleRef,
|
|
31562
|
+
onMove: setRotationHandleOffset,
|
|
31563
|
+
point: rotationHandleOffset,
|
|
31564
|
+
constrain: constrainToCircle
|
|
31565
|
+
});
|
|
31566
|
+
const [centerPx] = useTransformVectorsToPixels(center);
|
|
31567
|
+
const topLeftPx = mafs.vec.add(centerPx, centerToTopLeft);
|
|
31568
|
+
const angle = calculateAngleInDegrees(rotationHandleOffset) - calculateAngleInDegrees(centerToRotationHandle);
|
|
31569
|
+
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
31570
|
+
ref: draggableRef,
|
|
31571
|
+
transform: `translate(${topLeftPx[0]}, ${topLeftPx[1]}), rotate(${angle})`,
|
|
31572
|
+
style: {
|
|
31573
|
+
transformOrigin: `${-centerToTopLeft[0]}px ${-centerToTopLeft[1]}px`
|
|
31574
|
+
}
|
|
31575
|
+
}, /*#__PURE__*/React__namespace.createElement("image", {
|
|
31576
|
+
href: protractorImage
|
|
31577
|
+
}), /*#__PURE__*/React__namespace.createElement("g", {
|
|
31578
|
+
transform: `translate(5, ${-centerToTopLeft[1]})`,
|
|
31579
|
+
ref: rotationHandleRef
|
|
31580
|
+
}, /*#__PURE__*/React__namespace.createElement(RotationArrow, null)));
|
|
31581
|
+
}
|
|
31582
|
+
function RotationArrow() {
|
|
31583
|
+
const radius = 175;
|
|
31584
|
+
const angleDeg = 10;
|
|
31585
|
+
const angleRad = degreesToRadians(angleDeg);
|
|
31586
|
+
const endX = radius * (1 - Math.cos(angleRad));
|
|
31587
|
+
const endY = radius * -Math.sin(angleRad);
|
|
31588
|
+
const rotationArrow = pathBuilder().move(0, 0).circularArc(radius, endX, endY, {
|
|
31589
|
+
sweep: true
|
|
31590
|
+
}).build();
|
|
31591
|
+
const arrowhead = pathBuilder().move(-8, 0).line(0, 10).line(8, 0).build();
|
|
31592
|
+
const targetRadius = TARGET_SIZE / 2;
|
|
31593
|
+
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
31594
|
+
className: "protractor-rotation-handle"
|
|
31595
|
+
}, /*#__PURE__*/React__namespace.createElement("path", {
|
|
31596
|
+
className: "protractor-rotation-handle-arrow-arc",
|
|
31597
|
+
d: rotationArrow
|
|
31598
|
+
}), /*#__PURE__*/React__namespace.createElement("path", {
|
|
31599
|
+
className: "protractor-rotation-handle-arrowhead",
|
|
31600
|
+
d: arrowhead
|
|
31601
|
+
}), /*#__PURE__*/React__namespace.createElement("path", {
|
|
31602
|
+
className: "protractor-rotation-handle-arrowhead",
|
|
31603
|
+
d: arrowhead,
|
|
31604
|
+
transform: `translate(${endX}, ${endY}), rotate(${180 + angleDeg})`
|
|
31605
|
+
}), /*#__PURE__*/React__namespace.createElement("ellipse", {
|
|
31606
|
+
cx: "0px",
|
|
31607
|
+
cy: "-15px",
|
|
31608
|
+
rx: targetRadius,
|
|
31609
|
+
ry: targetRadius,
|
|
31610
|
+
fill: "none"
|
|
31611
|
+
}));
|
|
31612
|
+
}
|
|
31613
|
+
const protractorRadius = mafs.vec.mag(centerToRotationHandle);
|
|
31614
|
+
function constrainToCircle(edgePoint) {
|
|
31615
|
+
return mafs.vec.withMag(edgePoint, protractorRadius);
|
|
31616
|
+
}
|
|
31617
|
+
|
|
31618
|
+
/* The implementation of useDraggablePx is derived from
|
|
31619
|
+
* https://github.com/stevenpetryk/mafs/blob/4520319379a2cc2df8148d8baaef1f85db117103/src/interaction/useMovable.tsx#L20-L83
|
|
31620
|
+
* and copied here under the terms of the MIT license.
|
|
31621
|
+
*
|
|
31622
|
+
* Copyright 2021 Steven Petryk
|
|
31623
|
+
*
|
|
31624
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
31625
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
31626
|
+
* in the Software without restriction, including without limitation the rights
|
|
31627
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
31628
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
31629
|
+
* furnished to do so, subject to the following conditions:
|
|
31630
|
+
*
|
|
31631
|
+
* The above copyright notice and this permission notice shall be included in
|
|
31632
|
+
* all copies or substantial portions of the Software.
|
|
31633
|
+
*/
|
|
31634
|
+
function useDraggablePx(args) {
|
|
31635
|
+
const {
|
|
31636
|
+
gestureTarget: target,
|
|
31637
|
+
onMove,
|
|
31638
|
+
point,
|
|
31639
|
+
constrain = p => p
|
|
31640
|
+
} = args;
|
|
31641
|
+
const pickupPx = React__namespace.useRef([0, 0]);
|
|
31642
|
+
react.useDrag(state => {
|
|
31643
|
+
const {
|
|
31644
|
+
event,
|
|
31645
|
+
first,
|
|
31646
|
+
movement: pixelMovement
|
|
31647
|
+
} = state;
|
|
31648
|
+
event?.stopPropagation();
|
|
31649
|
+
if (first) {
|
|
31650
|
+
pickupPx.current = point;
|
|
31651
|
+
}
|
|
31652
|
+
if (mafs.vec.mag(pixelMovement) === 0) {
|
|
31653
|
+
return;
|
|
31654
|
+
}
|
|
31655
|
+
onMove?.(constrain(mafs.vec.add(pickupPx.current, pixelMovement)));
|
|
31656
|
+
}, {
|
|
31657
|
+
target,
|
|
31658
|
+
eventOptions: {
|
|
31659
|
+
passive: false
|
|
31660
|
+
}
|
|
31661
|
+
});
|
|
31662
|
+
}
|
|
31663
|
+
|
|
31664
|
+
// [L]inear Int[erp]olation: gets the weighted average of two values `a` and
|
|
31665
|
+
// `b`, with the given `fraction` specifying how much weight `a` and `b` get:
|
|
31666
|
+
// - if `fraction` is 0, `lerp` returns `a`.
|
|
31667
|
+
// - if `fraction` is 0.5, `lerp` returns the average of `a` and `b`.
|
|
31668
|
+
// - if `fraction` is 1, `lerp` returns `b`.
|
|
31669
|
+
function lerp(a, b, fraction) {
|
|
31670
|
+
return (b - a) * fraction + a;
|
|
31671
|
+
}
|
|
31672
|
+
function degreesToRadians(degrees) {
|
|
31673
|
+
return degrees / 180 * Math.PI;
|
|
31674
|
+
}
|
|
31675
|
+
|
|
31320
31676
|
function initializeGraphState(params) {
|
|
31321
31677
|
const {
|
|
31322
31678
|
graph,
|
|
@@ -32024,27 +32380,13 @@ function boundAndSnapToSides(destinationPoint, _ref3, index) {
|
|
|
32024
32380
|
return kmath.vector.add(coordsCopy[rel(-1)], offset);
|
|
32025
32381
|
}
|
|
32026
32382
|
|
|
32027
|
-
// Returns the closest point to the given `point` that is within the graph
|
|
32028
|
-
// bounds given in `state`.
|
|
32029
|
-
function bound$1(_ref4) {
|
|
32030
|
-
let {
|
|
32031
|
-
snapStep,
|
|
32032
|
-
range,
|
|
32033
|
-
point
|
|
32034
|
-
} = _ref4;
|
|
32035
|
-
const [requestedX, requestedY] = point;
|
|
32036
|
-
const [snapX, snapY] = snapStep;
|
|
32037
|
-
const [[minX, maxX], [minY, maxY]] = range;
|
|
32038
|
-
return [clamp(requestedX, minX + snapX, maxX - snapX), clamp(requestedY, minY + snapY, maxY - snapY)];
|
|
32039
|
-
}
|
|
32040
|
-
|
|
32041
32383
|
// Returns the vector from the given point to the top-right corner of the graph when snapped to the grid
|
|
32042
|
-
function maxMove(
|
|
32384
|
+
function maxMove(_ref4) {
|
|
32043
32385
|
let {
|
|
32044
32386
|
snapStep,
|
|
32045
32387
|
range,
|
|
32046
32388
|
point
|
|
32047
|
-
} =
|
|
32389
|
+
} = _ref4;
|
|
32048
32390
|
const topRight = bound$1({
|
|
32049
32391
|
snapStep,
|
|
32050
32392
|
range,
|
|
@@ -32054,12 +32396,12 @@ function maxMove(_ref5) {
|
|
|
32054
32396
|
}
|
|
32055
32397
|
|
|
32056
32398
|
// Returns the vector from the given point to the bottom-left corner of the graph when snapped to the grid
|
|
32057
|
-
function minMove(
|
|
32399
|
+
function minMove(_ref5) {
|
|
32058
32400
|
let {
|
|
32059
32401
|
snapStep,
|
|
32060
32402
|
range,
|
|
32061
32403
|
point
|
|
32062
|
-
} =
|
|
32404
|
+
} = _ref5;
|
|
32063
32405
|
const bottomLeft = bound$1({
|
|
32064
32406
|
snapStep,
|
|
32065
32407
|
range,
|
|
@@ -32067,15 +32409,6 @@ function minMove(_ref6) {
|
|
|
32067
32409
|
});
|
|
32068
32410
|
return mafs.vec.sub(bottomLeft, point);
|
|
32069
32411
|
}
|
|
32070
|
-
function clamp(value, min, max) {
|
|
32071
|
-
if (value < min) {
|
|
32072
|
-
return min;
|
|
32073
|
-
}
|
|
32074
|
-
if (value > max) {
|
|
32075
|
-
return max;
|
|
32076
|
-
}
|
|
32077
|
-
return value;
|
|
32078
|
-
}
|
|
32079
32412
|
const coordsOverlap = coords => coords.some((coord, i) => coords.some((c, j) => i !== j && kmath.vector.equal(coord, c)));
|
|
32080
32413
|
function updateAtIndex(args) {
|
|
32081
32414
|
const {
|
|
@@ -32209,6 +32542,10 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
|
|
|
32209
32542
|
dispatch(changeRange([[xMinRange, xMaxRange], [yMinRange, yMaxRange]]));
|
|
32210
32543
|
}, [dispatch, xMinRange, xMaxRange, yMinRange, yMaxRange]);
|
|
32211
32544
|
const numSegments = graph.type === "segment" ? graph.numSegments : null;
|
|
32545
|
+
const numSides = graph.type === "polygon" ? graph.numSides : null;
|
|
32546
|
+
const snapTo = graph.type === "polygon" ? graph.snapTo : null;
|
|
32547
|
+
const showAngles = graph.type === "polygon" ? graph.showAngles : null;
|
|
32548
|
+
const showSides = graph.type === "polygon" ? graph.showSides : null;
|
|
32212
32549
|
const originalPropsRef = React.useRef(props);
|
|
32213
32550
|
const latestPropsRef = wonderBlocksCore.useLatestRef(props);
|
|
32214
32551
|
React.useEffect(() => {
|
|
@@ -32219,7 +32556,7 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
|
|
|
32219
32556
|
if (latestPropsRef.current !== originalPropsRef.current) {
|
|
32220
32557
|
dispatch(reinitialize(latestPropsRef.current));
|
|
32221
32558
|
}
|
|
32222
|
-
}, [graph.type, numSegments, latestPropsRef]);
|
|
32559
|
+
}, [graph.type, numSegments, numSides, snapTo, showAngles, showSides, latestPropsRef]);
|
|
32223
32560
|
return /*#__PURE__*/React__namespace.createElement(MafsGraph, _extends({}, props, {
|
|
32224
32561
|
state: state,
|
|
32225
32562
|
dispatch: dispatch
|
|
@@ -32286,7 +32623,7 @@ const MafsGraph = props => {
|
|
|
32286
32623
|
}), props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLayer, {
|
|
32287
32624
|
lockedFigures: props.lockedFigures,
|
|
32288
32625
|
range: state.range
|
|
32289
|
-
}), renderGraph({
|
|
32626
|
+
}), "/* Protractor */", props.showProtractor && /*#__PURE__*/React__namespace.createElement(Protractor, null), renderGraph({
|
|
32290
32627
|
state,
|
|
32291
32628
|
dispatch
|
|
32292
32629
|
})))));
|
|
@@ -33582,14 +33919,9 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
33582
33919
|
return InteractiveGraph.validate(this.getUserInput?.(), rubric, this);
|
|
33583
33920
|
}
|
|
33584
33921
|
render() {
|
|
33585
|
-
if (this.props.graph.type === "polygon" && this.props.graph.numSides === "unlimited") {
|
|
33586
|
-
return /*#__PURE__*/React__namespace.createElement(LegacyInteractiveGraph, _extends({
|
|
33587
|
-
ref: this.legacyGraphRef
|
|
33588
|
-
}, this.props));
|
|
33589
|
-
}
|
|
33590
|
-
|
|
33591
33922
|
// Mafs shim
|
|
33592
|
-
|
|
33923
|
+
const mafsFlags = this.props.apiOptions?.flags?.["mafs"];
|
|
33924
|
+
if (shouldUseMafs(mafsFlags, this.props.graph)) {
|
|
33593
33925
|
const box = getInteractiveBoxFromSizeClass(this.props.containerSizeClass);
|
|
33594
33926
|
const gridStep = this.props.gridStep || Util.getGridStep(this.props.range, this.props.step, box[0]);
|
|
33595
33927
|
const snapStep = this.props.snapStep || Util.snapStepFromGridStep(gridStep);
|
|
@@ -34148,6 +34480,33 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
34148
34480
|
return props.graph;
|
|
34149
34481
|
}
|
|
34150
34482
|
}
|
|
34483
|
+
|
|
34484
|
+
// exported for testing
|
|
34485
|
+
function shouldUseMafs(mafsFlags, graph) {
|
|
34486
|
+
if (typeof mafsFlags === "boolean" || typeof mafsFlags === "undefined") {
|
|
34487
|
+
return false;
|
|
34488
|
+
}
|
|
34489
|
+
switch (graph.type) {
|
|
34490
|
+
case "point":
|
|
34491
|
+
if (graph.numPoints === UNLIMITED) {
|
|
34492
|
+
// TODO(benchristel): add a feature flag for the "unlimited"
|
|
34493
|
+
// case once we've implemented point graphs with unlimited
|
|
34494
|
+
// points
|
|
34495
|
+
return false;
|
|
34496
|
+
}
|
|
34497
|
+
return Boolean(mafsFlags["point"]);
|
|
34498
|
+
case "polygon":
|
|
34499
|
+
if (graph.numSides === UNLIMITED) {
|
|
34500
|
+
// TODO(benchristel): add a feature flag for the "unlimited"
|
|
34501
|
+
// case once we've implemented polygon graphs with unlimited
|
|
34502
|
+
// sides
|
|
34503
|
+
return false;
|
|
34504
|
+
}
|
|
34505
|
+
return Boolean(mafsFlags["polygon"]);
|
|
34506
|
+
default:
|
|
34507
|
+
return Boolean(mafsFlags[graph.type]);
|
|
34508
|
+
}
|
|
34509
|
+
}
|
|
34151
34510
|
var InteractiveGraph$1 = {
|
|
34152
34511
|
name: "interactive-graph",
|
|
34153
34512
|
displayName: "Interactive graph",
|
|
@@ -44387,7 +44746,7 @@ const init = function (options) {
|
|
|
44387
44746
|
|
|
44388
44747
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
44389
44748
|
const libName = "@khanacademy/perseus";
|
|
44390
|
-
const libVersion = "
|
|
44749
|
+
const libVersion = "24.1.0";
|
|
44391
44750
|
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
44392
44751
|
|
|
44393
44752
|
const apiVersion = {
|