@khanacademy/perseus-editor 0.0.1 → 0.1.2
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/.babelrc.js +1 -1
- package/CHANGELOG.md +26 -0
- package/dist/es/index.css +1020 -0
- package/dist/es/index.js +9 -1
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +1020 -0
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/strings.js +6 -1
- package/package.json +16 -4
- package/src/README.md +7 -0
- package/src/__stories__/editor.stories.js +33 -0
- package/src/__stories__/section-control-button.stories.jsx +27 -0
- package/src/__testdata__/input-number_testdata.js +28 -0
- package/src/__tests__/editor_test.jsx +123 -0
- package/src/__tests__/i18n_test.jsx +585 -0
- package/src/__tests__/traversal_test.jsx +378 -0
- package/src/all-editors.js +85 -0
- package/src/all-editors_test.js +17 -0
- package/src/all-widgets.js +10 -0
- package/src/article-editor.jsx +437 -0
- package/src/components/__stories__/blur-input.stories.jsx +24 -0
- package/src/components/blur-input.jsx +62 -0
- package/src/components/checkbox.jsx +194 -0
- package/src/components/device-framer.jsx +91 -0
- package/src/components/drag-target.jsx +88 -0
- package/src/components/dropdown-option.jsx +318 -0
- package/src/components/form-wrapped-text-field.jsx +248 -0
- package/src/components/graph-settings.jsx +631 -0
- package/src/components/hover-behavior.jsx +178 -0
- package/src/components/json-editor.jsx +121 -0
- package/src/components/link.jsx +173 -0
- package/src/components/simple-button.jsx +86 -0
- package/src/components/sortable.jsx +186 -0
- package/src/components/util.js +39 -0
- package/src/components/viewport-resizer.jsx +73 -0
- package/src/diffs/__stories__/perseus-diff-wrapper.jsx +8 -0
- package/src/diffs/__stories__/structured-item-diff.stories.jsx +855 -0
- package/src/diffs/__stories__/tags-diff.stories.jsx +38 -0
- package/src/diffs/__stories__/text-diff.stories.jsx +37 -0
- package/src/diffs/__tests__/split-diff_test.js +34 -0
- package/src/diffs/__tests__/string-array-diff_test.js +74 -0
- package/src/diffs/__tests__/structured-item-diff_test.js +432 -0
- package/src/diffs/__tests__/widget-diff-performer_test.js +44 -0
- package/src/diffs/article-diff.jsx +70 -0
- package/src/diffs/item-diff.jsx +77 -0
- package/src/diffs/renderer-diff.jsx +143 -0
- package/src/diffs/split-diff.jsx +30 -0
- package/src/diffs/string-array-diff.jsx +75 -0
- package/src/diffs/structured-item-diff.jsx +269 -0
- package/src/diffs/tags-diff.jsx +50 -0
- package/src/diffs/text-diff.jsx +194 -0
- package/src/diffs/widget-diff-performer.jsx +74 -0
- package/src/diffs/widget-diff.jsx +291 -0
- package/src/editor-page.jsx +351 -0
- package/src/editor.jsx +1364 -0
- package/src/hint-editor.jsx +447 -0
- package/src/i18n.jsx +139 -0
- package/src/iframe-content-renderer.jsx +201 -0
- package/src/index.js +27 -8
- package/src/item-editor.jsx +159 -0
- package/src/item-extras-editor.jsx +150 -0
- package/src/katex-error-view.jsx +87 -0
- package/src/multirenderer-editor.jsx +1074 -0
- package/src/section-control-button.jsx +53 -0
- package/src/stateful-article-editor.jsx +176 -0
- package/src/stateful-editor-page.jsx +90 -0
- package/src/styles/perseus-editor.less +1339 -0
- package/src/testing-widgets.js +12 -0
- package/src/util/register-all-widgets-and-editors-for-testing.js +15 -0
- package/src/util/register-all-widgets-and-editors-for-testing_test.js +18 -0
- package/src/widgets/__stories__/label-image-editor.stories.jsx +97 -0
- package/src/widgets/categorizer-editor.jsx +103 -0
- package/src/widgets/cs-program-editor.jsx +258 -0
- package/src/widgets/definition-editor.jsx +75 -0
- package/src/widgets/dropdown-editor.jsx +201 -0
- package/src/widgets/example-graphie-widget-editor.jsx +70 -0
- package/src/widgets/example-graphie-widget.jsx +175 -0
- package/src/widgets/example-widget-editor.jsx +60 -0
- package/src/widgets/example-widget.jsx +153 -0
- package/src/widgets/explanation-editor.jsx +89 -0
- package/src/widgets/expression-editor.jsx +560 -0
- package/src/widgets/graded-group-editor.jsx +162 -0
- package/src/widgets/graded-group-set-editor.jsx +101 -0
- package/src/widgets/grapher-editor.jsx +154 -0
- package/src/widgets/group-editor.jsx +83 -0
- package/src/widgets/iframe-editor.jsx +200 -0
- package/src/widgets/image-editor.jsx +326 -0
- package/src/widgets/input-number-editor.jsx +269 -0
- package/src/widgets/interaction/constraint-editor.jsx +147 -0
- package/src/widgets/interaction-editor.jsx +1389 -0
- package/src/widgets/interactive-graph-editor.jsx +322 -0
- package/src/widgets/label-image/__stories__/answer-choices.stories.jsx +81 -0
- package/src/widgets/label-image/__stories__/behavior.stories.jsx +32 -0
- package/src/widgets/label-image/__stories__/marker.stories.jsx +76 -0
- package/src/widgets/label-image/__stories__/question-markers.stories.jsx +106 -0
- package/src/widgets/label-image/__stories__/select-image.stories.jsx +60 -0
- package/src/widgets/label-image/answer-choices.jsx +231 -0
- package/src/widgets/label-image/behavior.jsx +92 -0
- package/src/widgets/label-image/marker.jsx +292 -0
- package/src/widgets/label-image/question-markers.jsx +181 -0
- package/src/widgets/label-image/select-image.jsx +86 -0
- package/src/widgets/label-image-editor.jsx +257 -0
- package/src/widgets/lights-puzzle-editor.jsx +267 -0
- package/src/widgets/matcher-editor.jsx +147 -0
- package/src/widgets/matrix-editor.jsx +155 -0
- package/src/widgets/measurer-editor.jsx +245 -0
- package/src/widgets/molecule-editor.jsx +63 -0
- package/src/widgets/number-line-editor.jsx +498 -0
- package/src/widgets/numeric-input-editor.jsx +554 -0
- package/src/widgets/orderer-editor.jsx +166 -0
- package/src/widgets/passage-editor.jsx +115 -0
- package/src/widgets/passage-ref-editor.jsx +77 -0
- package/src/widgets/passage-ref-target-editor.jsx +47 -0
- package/src/widgets/plotter-editor.jsx +537 -0
- package/src/widgets/radio/editor.jsx +398 -0
- package/src/widgets/reaction-diagram-editor.jsx +127 -0
- package/src/widgets/sequence-editor.jsx +205 -0
- package/src/widgets/simple-markdown-tester-editor.jsx +84 -0
- package/src/widgets/simple-markdown-tester.jsx +91 -0
- package/src/widgets/simulator-editor.jsx +144 -0
- package/src/widgets/sorter-editor.jsx +100 -0
- package/src/widgets/table-editor.jsx +164 -0
- package/src/widgets/transformer-editor.jsx +1006 -0
- package/src/widgets/unit-editor.jsx +293 -0
- package/src/widgets/video-editor.jsx +74 -0
- package/src/__tests__/index.test.js +0 -15
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import ExampleGraphieWidget from "./widgets/example-graphie-widget.jsx";
|
|
3
|
+
import ExampleWidget from "./widgets/example-widget.jsx";
|
|
4
|
+
import SimpleMarkdownTester from "./widgets/simple-markdown-tester.jsx";
|
|
5
|
+
|
|
6
|
+
import type {WidgetExports} from "@khanacademy/perseus";
|
|
7
|
+
|
|
8
|
+
export default ([
|
|
9
|
+
ExampleGraphieWidget,
|
|
10
|
+
ExampleWidget,
|
|
11
|
+
SimpleMarkdownTester,
|
|
12
|
+
]: $ReadOnlyArray<WidgetExports<>>);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
/**
|
|
3
|
+
* Some tests require some or all of the widgets and editors to be registered
|
|
4
|
+
* in order for them to work. Requiring this file will register all of the
|
|
5
|
+
* widgets and editors.
|
|
6
|
+
*/
|
|
7
|
+
import {Widgets} from "@khanacademy/perseus";
|
|
8
|
+
|
|
9
|
+
import allEditors from "../all-editors.js";
|
|
10
|
+
import allWidgets from "../all-widgets.js";
|
|
11
|
+
|
|
12
|
+
export const registerAllWidgetsAndEditorsForTesting = () => {
|
|
13
|
+
Widgets.registerWidgets(allWidgets);
|
|
14
|
+
Widgets.registerEditors(allEditors);
|
|
15
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import {Widgets} from "@khanacademy/perseus";
|
|
3
|
+
|
|
4
|
+
import {registerAllWidgetsAndEditorsForTesting} from "./register-all-widgets-and-editors-for-testing.js";
|
|
5
|
+
|
|
6
|
+
describe("Registering all widgets and editors", () => {
|
|
7
|
+
it("should have an editor for every widget", () => {
|
|
8
|
+
registerAllWidgetsAndEditorsForTesting();
|
|
9
|
+
|
|
10
|
+
const allWidgetsTypes = Widgets.getAllWidgetTypes();
|
|
11
|
+
|
|
12
|
+
expect(allWidgetsTypes).not.toContain("undefined");
|
|
13
|
+
|
|
14
|
+
for (const widgetType of allWidgetsTypes) {
|
|
15
|
+
expect(Widgets.getEditor(widgetType)).toBeTruthy();
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import {StyleSheet, css} from "aphrodite";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
import LabelImageEditor from "../label-image-editor.jsx";
|
|
6
|
+
|
|
7
|
+
import type {MarkerType} from "@khanacademy/perseus";
|
|
8
|
+
|
|
9
|
+
type StoryArgs = {||};
|
|
10
|
+
|
|
11
|
+
type Story = {|
|
|
12
|
+
title: string,
|
|
13
|
+
|};
|
|
14
|
+
|
|
15
|
+
export default ({
|
|
16
|
+
title: "Perseus/Editor/Widgets/Label Image Editor",
|
|
17
|
+
}: Story);
|
|
18
|
+
|
|
19
|
+
const styles = StyleSheet.create({
|
|
20
|
+
wrapper: {
|
|
21
|
+
// The maximum width of a widget in the editor.
|
|
22
|
+
width: 338,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
type State = {|
|
|
27
|
+
imageAlt: string,
|
|
28
|
+
choices: $ReadOnlyArray<string>,
|
|
29
|
+
imageUrl: string,
|
|
30
|
+
imageWidth: number,
|
|
31
|
+
imageHeight: number,
|
|
32
|
+
markers: $ReadOnlyArray<MarkerType>,
|
|
33
|
+
|};
|
|
34
|
+
|
|
35
|
+
class WithState extends React.Component<Empty, State> {
|
|
36
|
+
_widget: LabelImageEditor;
|
|
37
|
+
|
|
38
|
+
state = {
|
|
39
|
+
imageAlt: "Map of Europe",
|
|
40
|
+
choices: [
|
|
41
|
+
"Lamborghini",
|
|
42
|
+
"BMW",
|
|
43
|
+
"Volkswagen",
|
|
44
|
+
"Fiat",
|
|
45
|
+
"Porsche",
|
|
46
|
+
"Ferrari",
|
|
47
|
+
],
|
|
48
|
+
imageUrl:
|
|
49
|
+
"https://ka-perseus-images.s3.amazonaws.com/2ee5fc32e35c5178373b39fd304b325b2994c913.png",
|
|
50
|
+
imageWidth: 1280,
|
|
51
|
+
imageHeight: 1024,
|
|
52
|
+
markers: [
|
|
53
|
+
{
|
|
54
|
+
answers: ["BMW", "Volkswagen", "Porsche"],
|
|
55
|
+
label: "Germany",
|
|
56
|
+
x: 37.3,
|
|
57
|
+
y: 53.6,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
answers: [],
|
|
61
|
+
label: "",
|
|
62
|
+
x: 21,
|
|
63
|
+
y: 46,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
answers: ["Lamborghini", "Fiat", "Ferrari"],
|
|
67
|
+
label: "Italy",
|
|
68
|
+
x: 41.4,
|
|
69
|
+
y: 78.8,
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
render(): React.Node {
|
|
75
|
+
return (
|
|
76
|
+
<div className={css(styles.wrapper)}>
|
|
77
|
+
<LabelImageEditor
|
|
78
|
+
{...this.state}
|
|
79
|
+
onChange={(props) =>
|
|
80
|
+
this.setState({
|
|
81
|
+
...this._widget.serialize(),
|
|
82
|
+
...props,
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
// TODO(mdr): We found a new Flow error when upgrading:
|
|
86
|
+
// "widget (null) This type is incompatible with this._widget (LabelImageEditor)"
|
|
87
|
+
// $FlowFixMe[incompatible-type](0.52.0->0.53.0)
|
|
88
|
+
ref={(widget) => (this._widget = widget)}
|
|
89
|
+
/>
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const Default = (args: StoryArgs): React.Node => {
|
|
96
|
+
return <WithState />;
|
|
97
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import {
|
|
3
|
+
components,
|
|
4
|
+
ApiOptions,
|
|
5
|
+
Categorizer as CategorizerWidget,
|
|
6
|
+
Changeable,
|
|
7
|
+
EditorJsonify,
|
|
8
|
+
} from "@khanacademy/perseus";
|
|
9
|
+
import PropTypes from "prop-types";
|
|
10
|
+
import * as React from "react";
|
|
11
|
+
import _ from "underscore";
|
|
12
|
+
|
|
13
|
+
const {PropCheckBox, TextListEditor} = components;
|
|
14
|
+
const Categorizer = CategorizerWidget.widget;
|
|
15
|
+
|
|
16
|
+
type Props = $FlowFixMe;
|
|
17
|
+
|
|
18
|
+
class CategorizerEditor extends React.Component<Props> {
|
|
19
|
+
static propTypes = {
|
|
20
|
+
...Changeable.propTypes,
|
|
21
|
+
apiOptions: ApiOptions.propTypes,
|
|
22
|
+
items: PropTypes.arrayOf(PropTypes.string),
|
|
23
|
+
categories: PropTypes.arrayOf(PropTypes.string),
|
|
24
|
+
values: PropTypes.arrayOf(PropTypes.number),
|
|
25
|
+
randomizeItems: PropTypes.bool,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
static widgetName: string = "categorizer";
|
|
29
|
+
|
|
30
|
+
static defaultProps: Props = {
|
|
31
|
+
items: [],
|
|
32
|
+
categories: [],
|
|
33
|
+
values: [],
|
|
34
|
+
randomizeItems: false,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
change: ($FlowFixMe, $FlowFixMe, $FlowFixMe) => $FlowFixMe = (...args) => {
|
|
38
|
+
return Changeable.change.apply(this, args);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
serialize: () => $FlowFixMe = () => {
|
|
42
|
+
return EditorJsonify.serialize.call(this);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
render(): React.Node {
|
|
46
|
+
return (
|
|
47
|
+
<div>
|
|
48
|
+
<div className="perseus-widget-row">
|
|
49
|
+
<PropCheckBox
|
|
50
|
+
label="Randomize item order"
|
|
51
|
+
labelAlignment="right"
|
|
52
|
+
randomizeItems={this.props.randomizeItems}
|
|
53
|
+
onChange={this.props.onChange}
|
|
54
|
+
/>
|
|
55
|
+
</div>
|
|
56
|
+
Categories:
|
|
57
|
+
<TextListEditor
|
|
58
|
+
options={this.props.categories}
|
|
59
|
+
onChange={(cat) => {
|
|
60
|
+
this.change("categories", cat);
|
|
61
|
+
}}
|
|
62
|
+
layout="horizontal"
|
|
63
|
+
/>
|
|
64
|
+
Items:
|
|
65
|
+
<TextListEditor
|
|
66
|
+
options={this.props.items}
|
|
67
|
+
onChange={(items) => {
|
|
68
|
+
this.change({
|
|
69
|
+
items: items,
|
|
70
|
+
// TODO(eater): This truncates props.values so there
|
|
71
|
+
// are never more correct answers than items,
|
|
72
|
+
// ensuring the widget is possible to answer
|
|
73
|
+
// correctly. It doesn't necessarly keep each
|
|
74
|
+
// answer with its corresponding item if an item
|
|
75
|
+
// is deleted from the middle. Inconvenient, but
|
|
76
|
+
// it's at least possible for content creators to
|
|
77
|
+
// catch and fix.
|
|
78
|
+
values: _.first(this.props.values, items.length),
|
|
79
|
+
});
|
|
80
|
+
}}
|
|
81
|
+
layout="vertical"
|
|
82
|
+
/>
|
|
83
|
+
{/* There are a bunch of props that renderer.jsx passes to each widget
|
|
84
|
+
via widget-container.jsx that we aren't passing to Categorizer here.
|
|
85
|
+
See perseus-all-package/types.js#WidgetProps for details. */}
|
|
86
|
+
{/* $FlowFixMe[prop-missing] */}
|
|
87
|
+
<Categorizer
|
|
88
|
+
apiOptions={this.props.apiOptions}
|
|
89
|
+
items={this.props.items}
|
|
90
|
+
categories={this.props.categories}
|
|
91
|
+
values={this.props.values}
|
|
92
|
+
onChange={(newProps) => {
|
|
93
|
+
this.props.onChange(newProps);
|
|
94
|
+
}}
|
|
95
|
+
// eslint-disable-next-line react/jsx-no-bind
|
|
96
|
+
trackInteraction={function () {}}
|
|
97
|
+
/>
|
|
98
|
+
</div>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export default CategorizerEditor;
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/* eslint-disable no-useless-escape */
|
|
2
|
+
// @flow
|
|
3
|
+
/**
|
|
4
|
+
* This editor is for embedding Khan Academy CS programs.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
components,
|
|
9
|
+
Changeable,
|
|
10
|
+
Dependencies,
|
|
11
|
+
EditorJsonify,
|
|
12
|
+
Errors,
|
|
13
|
+
Log,
|
|
14
|
+
} from "@khanacademy/perseus";
|
|
15
|
+
import $ from "jquery";
|
|
16
|
+
import PropTypes from "prop-types";
|
|
17
|
+
import * as React from "react";
|
|
18
|
+
import _ from "underscore";
|
|
19
|
+
|
|
20
|
+
import BlurInput from "../components/blur-input.jsx";
|
|
21
|
+
|
|
22
|
+
const {InfoTip, PropCheckBox} = components;
|
|
23
|
+
|
|
24
|
+
const DEFAULT_WIDTH = 400;
|
|
25
|
+
const DEFAULT_HEIGHT = 400;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* This is used for editing a name/value pair.
|
|
29
|
+
*/
|
|
30
|
+
class PairEditor extends React.Component<$FlowFixMe> {
|
|
31
|
+
static propTypes = {
|
|
32
|
+
...Changeable.propTypes,
|
|
33
|
+
name: PropTypes.string,
|
|
34
|
+
value: PropTypes.string,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
static defaultProps = {
|
|
38
|
+
name: "",
|
|
39
|
+
value: "",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
change = (...args) => {
|
|
43
|
+
return Changeable.change.apply(this, args);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
serialize = () => {
|
|
47
|
+
return EditorJsonify.serialize.call(this);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
render(): React.Node {
|
|
51
|
+
return (
|
|
52
|
+
<fieldset className="pair-editor">
|
|
53
|
+
<label>
|
|
54
|
+
Name:{" "}
|
|
55
|
+
<BlurInput
|
|
56
|
+
value={this.props.name}
|
|
57
|
+
// $FlowFixMe[incompatible-type] single-param call returns a callback
|
|
58
|
+
onChange={this.change("name")}
|
|
59
|
+
/>
|
|
60
|
+
</label>
|
|
61
|
+
<label>
|
|
62
|
+
{" "}
|
|
63
|
+
Value:{" "}
|
|
64
|
+
<BlurInput
|
|
65
|
+
value={this.props.value}
|
|
66
|
+
// $FlowFixMe[incompatible-type] single-param call returns a callback
|
|
67
|
+
onChange={this.change("value")}
|
|
68
|
+
/>
|
|
69
|
+
</label>
|
|
70
|
+
</fieldset>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* This is used for editing a set of name/value pairs.
|
|
77
|
+
*/
|
|
78
|
+
class PairsEditor extends React.Component<$FlowFixMe> {
|
|
79
|
+
static propTypes = {
|
|
80
|
+
...Changeable.propTypes,
|
|
81
|
+
pairs: PropTypes.arrayOf(
|
|
82
|
+
PropTypes.shape({
|
|
83
|
+
name: PropTypes.string,
|
|
84
|
+
value: PropTypes.string,
|
|
85
|
+
}),
|
|
86
|
+
).isRequired,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
change = (...args) => {
|
|
90
|
+
return Changeable.change.apply(this, args);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
handlePairChange = (pairIndex, pair) => {
|
|
94
|
+
// If they're both non empty, add a new one
|
|
95
|
+
const pairs = this.props.pairs.slice();
|
|
96
|
+
pairs[pairIndex] = pair;
|
|
97
|
+
|
|
98
|
+
const lastPair = pairs[pairs.length - 1];
|
|
99
|
+
if (lastPair.name && lastPair.value) {
|
|
100
|
+
pairs.push({name: "", value: ""});
|
|
101
|
+
}
|
|
102
|
+
this.change("pairs", pairs);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
serialize = () => {
|
|
106
|
+
return EditorJsonify.serialize.call(this);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
render(): React.Node {
|
|
110
|
+
const editors = _.map(this.props.pairs, (pair, i) => {
|
|
111
|
+
return (
|
|
112
|
+
<PairEditor
|
|
113
|
+
key={i}
|
|
114
|
+
name={pair.name}
|
|
115
|
+
value={pair.value}
|
|
116
|
+
// eslint-disable-next-line react/jsx-no-bind
|
|
117
|
+
onChange={this.handlePairChange.bind(this, i)}
|
|
118
|
+
/>
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
return <div>{editors}</div>;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const KA_PROGRAM_URL = /khanacademy\.org\/computer-programming\/[^\/]+\/(\d+)/;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Given a program URL from the site, extract its program ID.
|
|
129
|
+
* If the input does not match the known URL patterns, it is assumed to be
|
|
130
|
+
* a program ID.
|
|
131
|
+
*/
|
|
132
|
+
function isolateProgramID(programUrl) {
|
|
133
|
+
const match = KA_PROGRAM_URL.exec(programUrl);
|
|
134
|
+
if (match) {
|
|
135
|
+
programUrl = match[1];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return programUrl;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* This is the main editor for this widget, to specify all the options.
|
|
143
|
+
*/
|
|
144
|
+
class CSProgramEditor extends React.Component<$FlowFixMe> {
|
|
145
|
+
static propTypes = {
|
|
146
|
+
...Changeable.propTypes,
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
static widgetName: string = "cs-program";
|
|
150
|
+
|
|
151
|
+
static defaultProps: $FlowFixMe = {
|
|
152
|
+
programID: "",
|
|
153
|
+
programType: null,
|
|
154
|
+
settings: [{name: "", value: ""}],
|
|
155
|
+
showEditor: false,
|
|
156
|
+
showButtons: false,
|
|
157
|
+
width: DEFAULT_WIDTH,
|
|
158
|
+
height: DEFAULT_HEIGHT,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
change: (...args: $ReadOnlyArray<mixed>) => $FlowFixMe = (...args) => {
|
|
162
|
+
// $FlowFixMe[incompatible-call]
|
|
163
|
+
return Changeable.change.apply(this, args);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
_handleSettingsChange: ($FlowFixMe) => void = (settings) => {
|
|
167
|
+
this.change({settings: settings.pairs});
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
_handleProgramIDChange: (string) => void = (programID) => {
|
|
171
|
+
programID = isolateProgramID(programID);
|
|
172
|
+
const {isDevServer, InitialRequestUrl} = Dependencies.getDependencies();
|
|
173
|
+
|
|
174
|
+
const host = isDevServer
|
|
175
|
+
? InitialRequestUrl.origin
|
|
176
|
+
: "https://www.khanacademy.org";
|
|
177
|
+
const baseUrl = `${host}/api/internal/scratchpads/${programID}`;
|
|
178
|
+
|
|
179
|
+
$.getJSON(baseUrl)
|
|
180
|
+
.done((programInfo) => {
|
|
181
|
+
const programType = programInfo.userAuthoredContentType;
|
|
182
|
+
this.change({
|
|
183
|
+
width: programInfo.width,
|
|
184
|
+
height: programInfo.height,
|
|
185
|
+
programID: programID,
|
|
186
|
+
programType: programType,
|
|
187
|
+
});
|
|
188
|
+
})
|
|
189
|
+
.fail((jqxhr, textStatus, error) => {
|
|
190
|
+
Log.error(
|
|
191
|
+
"Error retrieving scratchpad info for program ID ",
|
|
192
|
+
Errors.TransientService,
|
|
193
|
+
{
|
|
194
|
+
cause: error,
|
|
195
|
+
loggedMetadata: {
|
|
196
|
+
textStatus,
|
|
197
|
+
programID,
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
);
|
|
201
|
+
this.change({
|
|
202
|
+
width: DEFAULT_WIDTH,
|
|
203
|
+
height: DEFAULT_HEIGHT,
|
|
204
|
+
programID: programID,
|
|
205
|
+
programType: null,
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
serialize: () => $FlowFixMe = () => {
|
|
211
|
+
return EditorJsonify.serialize.call(this);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
render(): React.Node {
|
|
215
|
+
return (
|
|
216
|
+
<div>
|
|
217
|
+
<label>
|
|
218
|
+
Url or Program ID:{" "}
|
|
219
|
+
<BlurInput
|
|
220
|
+
value={this.props.programID}
|
|
221
|
+
onChange={this._handleProgramIDChange}
|
|
222
|
+
/>
|
|
223
|
+
</label>
|
|
224
|
+
<br />
|
|
225
|
+
<PropCheckBox
|
|
226
|
+
label="Show Editor"
|
|
227
|
+
showEditor={this.props.showEditor}
|
|
228
|
+
onChange={this.props.onChange}
|
|
229
|
+
/>
|
|
230
|
+
<InfoTip>
|
|
231
|
+
If you show the editor, you should use the "full-width"
|
|
232
|
+
alignment to make room for the width of the editor.
|
|
233
|
+
</InfoTip>
|
|
234
|
+
<br />
|
|
235
|
+
<PropCheckBox
|
|
236
|
+
label="Show Buttons"
|
|
237
|
+
showButtons={this.props.showButtons}
|
|
238
|
+
onChange={this.props.onChange}
|
|
239
|
+
/>
|
|
240
|
+
<br />
|
|
241
|
+
<label>
|
|
242
|
+
Settings:
|
|
243
|
+
<PairsEditor
|
|
244
|
+
name="settings"
|
|
245
|
+
pairs={this.props.settings}
|
|
246
|
+
onChange={this._handleSettingsChange}
|
|
247
|
+
/>
|
|
248
|
+
<InfoTip>
|
|
249
|
+
Settings that you add here are available to the program
|
|
250
|
+
as an object returned by <code>Program.settings()</code>
|
|
251
|
+
</InfoTip>
|
|
252
|
+
</label>
|
|
253
|
+
</div>
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export default CSProgramEditor;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import {components, Changeable, EditorJsonify} from "@khanacademy/perseus";
|
|
3
|
+
import PropTypes from "prop-types";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import _ from "underscore";
|
|
6
|
+
|
|
7
|
+
import Editor from "../editor.jsx";
|
|
8
|
+
|
|
9
|
+
const {TextInput} = components;
|
|
10
|
+
|
|
11
|
+
type Props = $FlowFixMe;
|
|
12
|
+
|
|
13
|
+
class DefinitionEditor extends React.Component<Props> {
|
|
14
|
+
static propTypes = {
|
|
15
|
+
...Changeable.propTypes,
|
|
16
|
+
togglePrompt: PropTypes.string,
|
|
17
|
+
definition: PropTypes.string,
|
|
18
|
+
apiOptions: PropTypes.any,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
static widgetName: string = "definition";
|
|
22
|
+
|
|
23
|
+
static defaultProps: Props = {
|
|
24
|
+
togglePrompt: "",
|
|
25
|
+
definition: "",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
change: ($FlowFixMe, $FlowFixMe, $FlowFixMe) => $FlowFixMe = (...args) => {
|
|
29
|
+
return Changeable.change.apply(this, args);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
serialize: () => $FlowFixMe = () => {
|
|
33
|
+
return EditorJsonify.serialize.call(this);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
render(): React.Node {
|
|
37
|
+
return (
|
|
38
|
+
<div className="perseus-widget-definition-editor">
|
|
39
|
+
<a
|
|
40
|
+
href="https://docs.google.com/document/d/1udaPef4imOfTMhmLDlWq4SM0mxL0r3YHFZE-5J1uGfo"
|
|
41
|
+
target="_blank"
|
|
42
|
+
>
|
|
43
|
+
Definition style guide
|
|
44
|
+
</a>
|
|
45
|
+
<div className="perseus-widget-row">
|
|
46
|
+
<label>
|
|
47
|
+
Word to be defined:{" "}
|
|
48
|
+
<TextInput
|
|
49
|
+
value={this.props.togglePrompt}
|
|
50
|
+
onChange={this.change("togglePrompt")}
|
|
51
|
+
placeholder="define me"
|
|
52
|
+
/>
|
|
53
|
+
</label>
|
|
54
|
+
</div>
|
|
55
|
+
<div className="perseus-widget-row">
|
|
56
|
+
<Editor
|
|
57
|
+
apiOptions={this.props.apiOptions}
|
|
58
|
+
content={this.props.definition}
|
|
59
|
+
widgetEnabled={false}
|
|
60
|
+
placeholder="definition goes here"
|
|
61
|
+
onChange={(props) => {
|
|
62
|
+
const newProps = {};
|
|
63
|
+
if (_.has(props, "content")) {
|
|
64
|
+
newProps.definition = props.content;
|
|
65
|
+
}
|
|
66
|
+
this.change(newProps);
|
|
67
|
+
}}
|
|
68
|
+
/>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export default DefinitionEditor;
|