diff --git a/Gemfile b/Gemfile
index 489d2bde51f4f..ee891ad7a6242 100644
--- a/Gemfile
+++ b/Gemfile
@@ -99,14 +99,14 @@ group :development, :test do
# For UI testing.
gem 'cucumber'
- gem 'eyes_selenium'
+ gem 'eyes_selenium', '3.17.20'
gem 'minitest', '~> 5.5'
gem 'minitest-around'
gem 'minitest-reporters', '~> 1.2.0.beta3'
gem 'net-http-persistent'
gem 'rinku'
gem 'rspec'
- gem 'selenium-webdriver'
+ gem 'selenium-webdriver', '3.141.0'
gem 'spring'
gem 'spring-commands-testunit'
gem 'webdrivers', '~> 3.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index 26688837e3dc6..d68cb99bdb24d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -366,7 +366,7 @@ GEM
crack (0.4.3)
safe_yaml (~> 1.0.0)
crass (1.0.4)
- css_parser (1.7.0)
+ css_parser (1.7.1)
addressable
cucumber (3.1.1)
builder (>= 2.1.2)
@@ -416,15 +416,20 @@ GEM
eventmachine (1.2.5)
execjs (2.7.0)
exifr (1.2.5)
- eyes_core (3.14.10)
+ eyes_core (3.17.20)
chunky_png (= 1.3.6)
faraday
+ faraday-cookie_jar
+ faraday_middleware
oily_png (~> 1.2)
oj
- eyes_selenium (3.14.10)
+ eyes_selenium (3.17.20)
+ crass
css_parser
- eyes_core (= 3.14.10)
+ eyes_core (= 3.17.20)
+ nokogiri
selenium-webdriver
+ state_machine
factory_girl (4.7.0)
activesupport (>= 3.0.0)
factory_girl_rails (4.7.0)
@@ -434,6 +439,11 @@ GEM
redis (~> 3.2)
faraday (0.15.4)
multipart-post (>= 1.2, < 3)
+ faraday-cookie_jar (0.0.7)
+ faraday (>= 0.8.0)
+ http-cookie (~> 1.0.0)
+ faraday_middleware (0.14.0)
+ faraday (>= 0.7.4, < 1.0)
ffi (1.10.0)
firebase (0.2.6)
httpclient
@@ -520,7 +530,7 @@ GEM
jsonapi-serializers (1.0.0)
activesupport
jumphash (0.1.0)
- jwt (2.1.0)
+ jwt (2.2.2)
kaminari (0.17.0)
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
@@ -561,7 +571,7 @@ GEM
multi_json (1.13.1)
multi_test (0.1.2)
multi_xml (0.5.5)
- multipart-post (2.0.0)
+ multipart-post (2.1.1)
mustermann (1.0.3)
mysql2 (0.5.2)
nakayoshi_fork (0.0.4)
@@ -574,7 +584,7 @@ GEM
net_http_ssl_fix (0.0.10)
netrc (0.11.0)
nio4r (2.3.1)
- nokogiri (1.10.3)
+ nokogiri (1.10.10)
mini_portile2 (~> 2.4.0)
oauth (0.5.1)
oauth2 (1.4.1)
@@ -812,6 +822,7 @@ GEM
sshkit (1.20.0)
net-scp (>= 1.1.2)
net-ssh (>= 2.8.0)
+ state_machine (1.2.0)
stringex (2.5.2)
swd (1.0.1)
activesupport (>= 3)
@@ -833,8 +844,8 @@ GEM
tilt (2.0.10)
timecop (0.8.1)
ttfunk (1.4.0)
- twilio-ruby (5.20.1)
- faraday (~> 0.9)
+ twilio-ruby (5.40.1)
+ faraday (>= 0.9, < 2.0)
jwt (>= 1.5, <= 2.5)
nokogiri (>= 1.6, < 2.0)
tzinfo (1.2.5)
@@ -939,7 +950,7 @@ DEPENDENCIES
devise_invitable (~> 1.6.0)
dotiw
execjs
- eyes_selenium
+ eyes_selenium (= 3.17.20)
factory_girl_rails
fakeredis
firebase
@@ -1039,7 +1050,7 @@ DEPENDENCIES
scss_lint
sdoc
seamless_database_pool!
- selenium-webdriver
+ selenium-webdriver (= 3.141.0)
sequel
shotgun
simplecov (~> 0.9)
diff --git a/apps/src/blocklyAddons/cdoBlockSvg.js b/apps/src/blocklyAddons/cdoBlockSvg.js
new file mode 100644
index 0000000000000..8caab445bcb65
--- /dev/null
+++ b/apps/src/blocklyAddons/cdoBlockSvg.js
@@ -0,0 +1,25 @@
+import GoogleBlockly from 'blockly/core';
+
+export default class BlockSvg extends GoogleBlockly.BlockSvg {
+ getTitles() {
+ let fields = [];
+ this.inputList.forEach(input => {
+ input.fieldRow.forEach(field => {
+ fields.push(field);
+ });
+ });
+ return fields;
+ }
+
+ getTitleValue(name) {
+ return super.getFieldValue(name);
+ }
+
+ isUserVisible() {
+ return false; // TODO
+ }
+
+ setHSV(h, s, v) {
+ return super.setColour(Blockly.utils.colour.hsvToHex(h, s, v * 255));
+ }
+}
diff --git a/apps/src/blocklyAddons/cdoFieldDropdown.js b/apps/src/blocklyAddons/cdoFieldDropdown.js
new file mode 100644
index 0000000000000..ee6a8468c4745
--- /dev/null
+++ b/apps/src/blocklyAddons/cdoFieldDropdown.js
@@ -0,0 +1,19 @@
+import GoogleBlockly from 'blockly/core';
+
+export default class FieldDropdown extends GoogleBlockly.FieldDropdown {
+ constructor(menuGenerator, opt_changeHandler, opt_alwaysCallChangeHandler) {
+ let validator;
+ if (opt_changeHandler) {
+ validator = function(val) {
+ if (
+ this.getSourceBlock() &&
+ !this.getSourceBlock().isInsertionMarker_ &&
+ this.value_ !== val
+ ) {
+ opt_changeHandler(val);
+ }
+ };
+ }
+ super(menuGenerator, validator);
+ }
+}
diff --git a/apps/src/blocklyAddons/cdoInput.js b/apps/src/blocklyAddons/cdoInput.js
new file mode 100644
index 0000000000000..c1a84cfd6fa63
--- /dev/null
+++ b/apps/src/blocklyAddons/cdoInput.js
@@ -0,0 +1,7 @@
+import GoogleBlockly from 'blockly/core';
+
+export default class Input extends GoogleBlockly.Input {
+ appendTitle(a, b) {
+ return super.appendField(a, b);
+ }
+}
diff --git a/apps/src/blocklyAddons/cdoWorkspaceSvg.js b/apps/src/blocklyAddons/cdoWorkspaceSvg.js
new file mode 100644
index 0000000000000..580a2af7870f6
--- /dev/null
+++ b/apps/src/blocklyAddons/cdoWorkspaceSvg.js
@@ -0,0 +1,22 @@
+import GoogleBlockly from 'blockly/core';
+
+export default class WorkspaceSvg extends GoogleBlockly.WorkspaceSvg {
+ addUnusedBlocksHelpListener() {} // TODO
+ getAllUsedBlocks() {
+ return super.getAllBlocks();
+ }
+ getToolboxWidth() {
+ return Blockly.mainBlockSpace.getMetrics().toolboxWidth;
+ }
+ isReadOnly() {
+ return false; // TODO
+ }
+ setEnableToolbox() {} // TODO
+}
+
+WorkspaceSvg.prototype.blockSpaceEditor = {
+ blockLimits: {
+ blockLimitExceeded: () => false, // TODO
+ getLimit: () => {} // TODO
+ }
+};
diff --git a/apps/src/lib/levelbuilder/TextareaWithMarkdownPreview.jsx b/apps/src/lib/levelbuilder/TextareaWithMarkdownPreview.jsx
new file mode 100644
index 0000000000000..b6d12e238ddd3
--- /dev/null
+++ b/apps/src/lib/levelbuilder/TextareaWithMarkdownPreview.jsx
@@ -0,0 +1,71 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import SafeMarkdown from '@cdo/apps/templates/SafeMarkdown';
+import color from '@cdo/apps/util/color';
+
+const styles = {
+ box: {
+ marginTop: 10,
+ marginBottom: 10,
+ border: '1px solid ' + color.light_gray,
+ padding: 10
+ },
+ input: {
+ width: '100%',
+ boxSizing: 'border-box',
+ padding: '4px 6px',
+ color: '#555',
+ border: '1px solid #ccc',
+ borderRadius: 4,
+ margin: 0
+ }
+};
+
+/**
+ * Component for previewing Markdown for a edit field
+ */
+export default class TextareaWithMarkdownPreview extends React.Component {
+ static propTypes = {
+ markdown: PropTypes.string.isRequired,
+ label: PropTypes.string.isRequired,
+ name: PropTypes.string.isRequired,
+ inputRows: PropTypes.number
+ };
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ markdown: this.props.markdown
+ };
+ }
+
+ handleMarkdownChange = event => {
+ this.setState({markdown: event.target.value});
+ };
+
+ render() {
+ return (
+
+ );
+ }
+}
diff --git a/apps/src/lib/levelbuilder/course-editor/CourseEditor.js b/apps/src/lib/levelbuilder/course-editor/CourseEditor.js
index f73e27817851d..102bef648db88 100644
--- a/apps/src/lib/levelbuilder/course-editor/CourseEditor.js
+++ b/apps/src/lib/levelbuilder/course-editor/CourseEditor.js
@@ -6,7 +6,7 @@ import {resourceShape} from '@cdo/apps/templates/courseOverview/resourceType';
import VisibleAndPilotExperiment from '@cdo/apps/lib/levelbuilder/script-editor/VisibleAndPilotExperiment';
import HelpTip from '@cdo/apps/lib/ui/HelpTip';
import color from '@cdo/apps/util/color';
-import MarkdownPreview from '@cdo/apps/lib/levelbuilder/script-editor/MarkdownPreview';
+import TextareaWithMarkdownPreview from '@cdo/apps/lib/levelbuilder/TextareaWithMarkdownPreview';
const styles = {
input: {
@@ -53,23 +53,6 @@ export default class CourseEditor extends Component {
versionYearOptions: PropTypes.arrayOf(PropTypes.string).isRequired
};
- constructor(props) {
- super(props);
-
- this.state = {
- descriptionStudent: this.props.descriptionStudent,
- descriptionTeacher: this.props.descriptionTeacher
- };
- }
-
- handleTeacherDescriptionChange = event => {
- this.setState({descriptionTeacher: event.target.value});
- };
-
- handleStudentDescriptionChange = event => {
- this.setState({descriptionStudent: event.target.value});
- };
-
render() {
const {
name,
@@ -134,28 +117,18 @@ export default class CourseEditor extends Component {
style={styles.input}
/>
-
-
+
+
Basic settings
-
+
Basic Settings