Skip to content

Commit

Permalink
Full-Screen Tutorial Editor (#238)
Browse files Browse the repository at this point in the history
* Teacher mode
* Tutorial editor overlay
* clEditor for WYSIWYG HTML editing
* Saves tutorial when file is saved
  • Loading branch information
adamcarheden authored and MarkusBordihn committed Jun 19, 2019
1 parent d5f524d commit 9bd5ca4
Show file tree
Hide file tree
Showing 32 changed files with 2,473 additions and 281 deletions.
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
'Sk': true,
'brython': true,
'chrome': true,
'clEditor': true,
'cwc': true,
'goog': true,
'i18t': true,
Expand Down
3 changes: 3 additions & 0 deletions app/default/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@
<!-- Marked -->
<script src="external/marked/marked.min.js"></script>

<!-- cl-editor -->
<script src="external/cl-editor/index.min.js"></script>

<!-- Blockly -->
<script src="external/blockly/blockly_compressed.js"></script>
<script src="external/blockly/blocks_compressed.js"></script>
Expand Down
1 change: 1 addition & 0 deletions build/cwc/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ closureBuilder.build({
'build/externs/addons.js',
'build/externs/blockly.js',
'build/externs/chrome.js',
'build/externs/clEditor.js',
'build/externs/codemirror.js',
'build/externs/coffeescript.js',
'build/externs/global.js',
Expand Down
2 changes: 1 addition & 1 deletion build/cwc/tutorials.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ let replacePlaceholders = function(obj, pwd) {
}
};

console.log('Processe Tutorials from', inDir);
console.log('Process Tutorials from', inDir);
procTemplates(inDir, function(template) {
fs.readFile(template, 'utf8', function(err, data) {
if (err) {
Expand Down
10 changes: 10 additions & 0 deletions build/external/extra_files.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,4 +193,14 @@ closureBuilder.build({
out: 'genfiles/third_party/external/marked',
});

/**
* cl-editor
*/
closureBuilder.build({
name: 'clEditor',
resources: [
'node_modules/cl-editor/dist/index.min.js',
],
out: 'genfiles/third_party/external/cl-editor',
});

30 changes: 30 additions & 0 deletions build/externs/clEditor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* @fileoverview CL Editor externs.
* @externs
*
* @license Copyright 2019 The Coding with Chrome Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @author carheden@google.com (Adam Carheden)
*/


/**
* @param {Object} options
* @constructor
*/
let clEditor = function(options) {};

/** @type {Function} */
clEditor.prototype.getHtml = function() {};
1 change: 1 addition & 0 deletions locales/eng/sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* @author mbordihn@google.com (Markus Bordihn)
*/
Locales['eng']['SIDEBAR'] = {
'ADD_TUTORIAL': 'Add tutorial',
'FILE_DESCRIPTION': 'Show file description',
'LIBRARY': 'Open file library',
'MEDIA': 'Upload and insert media',
Expand Down
64 changes: 64 additions & 0 deletions locales/eng/tutorial_editor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* @fileoverview Translation file for the Setting screen (English).
*
* @license Copyright 2019 The Coding with Chrome Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @author carheden@google.com (Adam Carheden)
*/
/* eslint-disable max-len */
Locales['eng']['TUTORIAL_EDITOR'] = {
'ADD_ATTACHMENT': 'Add Image/Video',
'ADD_STEP': 'Add New Step',
'ADD_YOUTUBE': 'Add YouTube',
'CLOSE': 'Close Without Saving',
'DESCRIPTION': 'Description',
'HELP_EXAMPLE_CODE': `You may provide example code for each step of a lesson.
Example code will be automatically loaded into the editor when the step is
activated if the editor is either empty or matches the code of the previous
step. The student can re-load the example code into the editor at any time
via a button click.`.replace(/\n/g, ' '),
'HELP_VALIDATION': `Validation allows you to give students feedback. It runs
every time the student changes the editor. You can either match the text
output of the student's code or run a custom JavaScript function. When
validation succeeds, the student sees a message at the bottom of the step.`
.replace(/\n/g, ' '),
'HELP_MATCH_TEXT': `Text matching validation compares the text output of the
student's code to text you provide. In blockly, text output is text written
by the Command/write() and Command/writeLine() blocks. In JavaScript, text
output is text written by the document.write() function or any text-based
DOM elements the code adds to the page. Text matching isn't supported for
python at this time.`.replace(/\n/g, ' '),
'HELP_RUN_CODE': `You can validate the student's code by running your own code
in the context of the preview window. Your code can either inspect the
student's code directly, inspect the DOM generated by the student's code or
both. It should return a JavaScript object with the keys 'message' and
'solved'. 'message' is the text to display. 'solved' determines if the
message is displayed in a green box, indicating the student's code is
correct, or a red box, indicating the code is not yet correct.`
.replace(/\n/g, ' '), 'EXAMPLE_CODE': 'Example Code',
'LOAD_VALIDATION_TEMPLATE': 'Load Validation Template',
'MATCH_TEXT': 'Match Text',
'MEDIA': 'Media',
'MOVE_STEP_UP': 'Move Step Up',
'MOVE_STEP_DOWN': 'Move Step Down',
'REMOVE_STEP': 'Remove Step',
'RUN_CODE': 'Run Code',
'SAVE': 'Save and Close',
'TITLE': 'Title',
'VALIDATION': 'Validation',
'SUCCESS_MESSAGE': 'Message On Success',
'SUCCESS_MESSAGE_EXAMPLE': 'Great Job!',
'STEP': 'Step',
};
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -140,5 +140,8 @@
"usage": "node build/usage.js",
"web-app": "npm run build:core && npm run build:cwc-main && npm run build:web_app && npm run launch:web",
"web-app-debug": "npm run build:core && npm run build:cwc-main-debug && npm run build:web_app && npm run launch:web"
},
"dependencies": {
"cl-editor": "^1.3.1"
}
}
3 changes: 2 additions & 1 deletion src/addon/workbench/workbench.js
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,8 @@ cwc.addon.Workbench.prototype.loadProjectAsTutorial_ =
},
'steps': steps,
};
tutorialInstance.setTutorial(tutorialSpec, this.imagesDb_);
tutorialInstance.setTutorial(tutorialSpec, this.helper.getUserLanguage(),
this.imagesDb_);
});
};

Expand Down
1 change: 1 addition & 0 deletions src/config/user_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ cwc.userConfigName = {
SKIP_WELCOME: 'skip_welcome',
SKIP_WHATS_NEW: 'skip_whats_new',
SPHERO: 'sphero',
TEACHER_MODE: 'teacher_mode',
WORKBENCH_FETCH: 'workbench_fetch',
ZOOM: 'zoom',
};
Expand Down
6 changes: 5 additions & 1 deletion src/file_format/file_format.js
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,11 @@ cwc.fileFormat.File.prototype.setMetadata = function(name, value,
if (!(namespace in this.metadata_)) {
this.metadata_[namespace] = {};
}
this.metadata_[namespace][name] = value;
if (name) {
this.metadata_[namespace][name] = value;
} else {
this.metadata_[namespace] = value;
}
return this;
};

Expand Down
2 changes: 1 addition & 1 deletion src/file_handler/file_loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ cwc.fileHandler.FileLoader.prototype.loadCWCFile = async function(data,
let tutorialInstance = this.helper.getInstance('tutorial');
let tutorialAvailable = file.getTutorial(userLanguage);
if (tutorialInstance && tutorialAvailable) {
tutorialInstance.setTutorial(file.getTutorial(userLanguage));
tutorialInstance.setTutorial(file.getTutorial(userLanguage), userLanguage);
}

// Handle sidebar icons
Expand Down
30 changes: 19 additions & 11 deletions src/file_handler/file_saver.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,10 @@ cwc.fileHandler.FileSaver = function(helper) {
* @param {boolean=} autoDetect Auto detect where to save the file.
* @export
*/
cwc.fileHandler.FileSaver.prototype.saveFile = function(autoDetect = false) {
this.prepareContent();
cwc.fileHandler.FileSaver.prototype.saveFile = async function(
autoDetect = false) {
this.log_.info('saveFile...');
await this.prepareContent();
if (autoDetect && this.gDriveId) {
this.saveGDriveFile(true);
} else if (this.fileHandler) {
Expand All @@ -76,8 +78,9 @@ cwc.fileHandler.FileSaver.prototype.saveFile = function(autoDetect = false) {
/**
* @export
*/
cwc.fileHandler.FileSaver.prototype.saveFileAs = function() {
this.prepareContent();
cwc.fileHandler.FileSaver.prototype.saveFileAs = async function() {
this.log_.info('saveFileAs...');
await this.prepareContent();
this.selectFileToSave(this.filename, this.fileData);
};

Expand All @@ -87,10 +90,10 @@ cwc.fileHandler.FileSaver.prototype.saveFileAs = function() {
* file dialog.
* @export
*/
cwc.fileHandler.FileSaver.prototype.saveGDriveFile = function(save_file) {
this.log_.info('Save file', this.filename, 'in Google Drive', this.gDriveId);
cwc.fileHandler.FileSaver.prototype.saveGDriveFile = async function(save_file) {
this.log_.info('Save file in Google Drive', this.gDriveId);
let gDriveInstance = this.helper.getInstance('gapi').getDrive();
this.prepareContent();
await this.prepareContent();
if (save_file) {
gDriveInstance.saveFile(this.filename, this.fileData, this.gDriveId);
} else {
Expand All @@ -102,18 +105,18 @@ cwc.fileHandler.FileSaver.prototype.saveGDriveFile = function(save_file) {
/**
* @export
*/
cwc.fileHandler.FileSaver.prototype.saveGCloudFile = function() {
this.log_.info('Save file', this.filename, 'in Google Cloud');
cwc.fileHandler.FileSaver.prototype.saveGCloudFile = async function() {
this.log_.info('Save file in Google Cloud');
let gCloudInstance = this.helper.getInstance('gapi').getCloud();
this.prepareContent();
await this.prepareContent();
gCloudInstance.publishDialog(this.filename, this.fileData, this.mimeType);
};


/**
* Prepares file and ensures we have the latest editor content.
*/
cwc.fileHandler.FileSaver.prototype.prepareContent = function() {
cwc.fileHandler.FileSaver.prototype.prepareContent = async function() {
this.log_.info('Prepare Content for saving file...');
let editorInstance = this.helper.getInstance('editor');
let fileInstance = this.helper.getInstance('file');
Expand Down Expand Up @@ -143,6 +146,11 @@ cwc.fileHandler.FileSaver.prototype.prepareContent = function() {
}
}

let tutorialInstance = this.helper.getInstance('tutorial');
if (tutorialInstance) {
await tutorialInstance.prepareForSave();
}

this.fileData = file.getJSON();
this.filename = this.addFileExtension(filename || fileTitle || 'untitled');
} else {
Expand Down
2 changes: 2 additions & 0 deletions src/ui/setting_screen/setting_screen.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ cwc.ui.SettingScreen.prototype.setUserConfig = function() {
cwc.userConfigName.DEBUG_MODE);
this.setConfig_('workbench-fetch', cwc.userConfigType.GENERAL,
cwc.userConfigName.WORKBENCH_FETCH);
this.setConfig_('teacher-mode', cwc.userConfigType.GENERAL,
cwc.userConfigName.TEACHER_MODE);
};


Expand Down
8 changes: 8 additions & 0 deletions src/ui/setting_screen/setting_screen.soy
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,14 @@
{param id: 'workbench-fetch' /}
{param opt_restart: true /}
{/call}

{call .setting_ data="all"}
{param title: 'Teacher Mode' /}
{param text: 'Allow tutorials to be edited' /}
{param type: 'switch' /}
{param id: 'teacher-mode' /}
{param opt_restart: true /}
{/call}
</ul>

<div class="mdl-card__supporting-text">
Expand Down
23 changes: 22 additions & 1 deletion src/ui/sidebar/sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ cwc.ui.Sidebar.prototype.decorate = function(node) {
this.nodeContent = goog.dom.getElement(this.prefix + 'content');
this.nodeCustomIcons = goog.dom.getElement(this.prefix + 'icons-custom');
this.hideContent();
this.showTutorialButtons();

// File Library
this.events_.listen('library-button', goog.events.EventType.CLICK, () => {
Expand Down Expand Up @@ -157,6 +158,12 @@ cwc.ui.Sidebar.prototype.decorate = function(node) {
this.helper.getInstance('tutorial').startTutorial();
}
});

// Add Tutorial
this.events_.listen('add_tutorial-button', goog.events.EventType.CLICK,
() => {
this.helper.getInstance('tutorial').newTutorial();
});
};


Expand Down Expand Up @@ -224,6 +231,20 @@ cwc.ui.Sidebar.prototype.enableButton = function(id, enabled) {
cwc.ui.Helper.enableElement(this.prefix + id + '-button', enabled);
};

/**
* Shows/hides the tutorial and add tutorial buttons based on whether or not
* there is a tutorial and if teacher mode is enabled. It should ensure that
* only one or the other are visable at a time.
*/
cwc.ui.Sidebar.prototype.showTutorialButtons = function() {
let userConfigInstance = this.helper.getInstance('userConfig');
let tutorialInstance = this.helper.getInstance('tutorial');
let teacherMode = userConfigInstance.get(cwc.userConfigType.GENERAL,
cwc.userConfigName.TEACHER_MODE);
let canAddTutorial = teacherMode && !tutorialInstance.hasTutorial();
this.showButton('add_tutorial', canAddTutorial);
this.showButton('tutorial', !canAddTutorial);
};

/**
* @param {boolean} enabled
Expand All @@ -246,6 +267,7 @@ cwc.ui.Sidebar.prototype.enableTour = function(enabled) {
*/
cwc.ui.Sidebar.prototype.enableTutorial = function(enabled) {
this.enableButton('tutorial', enabled);
this.showTutorialButtons();
};


Expand All @@ -266,7 +288,6 @@ cwc.ui.Sidebar.prototype.showButton = function(id, visible) {
goog.dom.getElement(this.prefix + id), visible);
};


/**
* @param {boolean} visible
*/
Expand Down
6 changes: 6 additions & 0 deletions src/ui/sidebar/sidebar.soy
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@
{param tooltip: '@@SIDEBAR__TUTORIAL' /}
{/call}

{call .sidebarIcon data="all"}
{param id: 'add_tutorial' /}
{param icon: 'playlist_add' /}
{param tooltip: '@@SIDEBAR__ADD_TUTORIAL' /}
{/call}

<div class="{$prefix}icons-seperator"></div>

<div id="{$prefix}icons-custom"></div>
Expand Down
Loading

0 comments on commit 9bd5ca4

Please sign in to comment.