Skip to content

Commit 1f850fa

Browse files
adamcarhedenMarkusBordihn
authored andcommitted
Prompt before loading tutorial example code (#189)
1 parent 2e96b60 commit 1f850fa

File tree

6 files changed

+326
-112
lines changed

6 files changed

+326
-112
lines changed

src/mode/modder.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ cwc.mode.Modder.prototype.loadMode = function(mode) {
7676
/**
7777
* @param {cwc.mode.Type} mode
7878
* @return {!Promise}
79+
* @export
7980
*/
8081
cwc.mode.Modder.prototype.setMode = function(mode) {
8182
let modeConfig = cwc.mode.Config.get(mode);
@@ -187,6 +188,7 @@ cwc.mode.Modder.prototype.setFilename = function(filename) {
187188

188189
/**
189190
* @param {cwc.mode.Type=} mode
191+
* @export
190192
*/
191193
cwc.mode.Modder.prototype.postMode = function(mode = this.mode) {
192194
this.log_.info('Post handling for', mode);
@@ -285,6 +287,7 @@ cwc.mode.Modder.prototype.addBlocklyView = function(name, content) {
285287
* @param {string=} content
286288
* @param {cwc.ui.EditorType=} type
287289
* @param {cwc.ui.EditorHint=} hints
290+
* @export
288291
*/
289292
cwc.mode.Modder.prototype.addEditorView = function(name, content, type, hints) {
290293
let editorInstance = this.helper.getInstance('editor');
@@ -296,6 +299,7 @@ cwc.mode.Modder.prototype.addEditorView = function(name, content, type, hints) {
296299

297300
/**
298301
* @param {string} name
302+
* @export
299303
*/
300304
cwc.mode.Modder.prototype.setEditorView = function(name) {
301305
let editorInstance = this.helper.getInstance('editor');

src/ui/editor/editor.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ cwc.ui.Editor.prototype.setLocalHints = function(hints) {
306306
/**
307307
* @param {string=} name
308308
* @return {Object|string}
309+
* @export
309310
*/
310311
cwc.ui.Editor.prototype.getEditorContent = function(name) {
311312
let editorContent = {};
@@ -330,6 +331,7 @@ cwc.ui.Editor.prototype.getEditorContent = function(name) {
330331
/**
331332
* @param {string} content
332333
* @param {string=} view
334+
* @export
333335
*/
334336
cwc.ui.Editor.prototype.setEditorContent = function(content,
335337
view = cwc.ui.EditorContent.DEFAULT) {
@@ -502,6 +504,7 @@ cwc.ui.Editor.prototype.getViews = function() {
502504

503505
/**
504506
* @return {string}
507+
* @export
505508
*/
506509
cwc.ui.Editor.prototype.getCurrentView = function() {
507510
return this.currentEditorView;

src/ui/tutorial/tutorial.gss

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,6 @@
1818
*/
1919

2020

21-
#{$prefix}addon-tutorial-content h3 {
22-
margin: 10px;
23-
}
24-
2521
.{$prefix}tutorial-step-message {
2622
color: #ffffff;
2723
text-align: center;
@@ -65,6 +61,9 @@
6561
margin-top: 24px;
6662
}
6763

64+
.{$prefix}tutorial-step-actions button {
65+
margin-right: 0.5em
66+
}
6867
.{$prefix}tutorial-step-container {
6968
counter-increment: step-number;
7069
padding-bottom: 20px;

src/ui/tutorial/tutorial.js

Lines changed: 85 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,7 @@ cwc.ui.Tutorial.prototype.startTutorial = function() {
443443
youtube_videos: (step.videos || []).map((video) =>
444444
video['youtube_id']
445445
),
446+
hasCode: step.code && step.code.trim().length > 0,
446447
})),
447448
}
448449
);
@@ -501,8 +502,8 @@ cwc.ui.Tutorial.prototype.initMediaOverlay_ = function() {
501502
*/
502503
cwc.ui.Tutorial.prototype.initMedia_ = function() {
503504
this.initMediaOverlay_();
504-
let nodeListImages = this.rootNode_.querySelectorAll(
505-
'.js-project-step-image');
505+
let nodeListImages = this.rootNode_.querySelectorAll('.' + this.prefix +
506+
'step-image');
506507
if (this.imagesDb_) {
507508
[].forEach.call(nodeListImages, (image) => {
508509
let imageSrc = image.getAttribute('data-src');
@@ -526,16 +527,16 @@ cwc.ui.Tutorial.prototype.initMedia_ = function() {
526527
*/
527528
cwc.ui.Tutorial.prototype.initSteps_ = function() {
528529
let prefix = this.prefix + 'step-';
530+
let classPrefix = '.'+prefix;
529531
this.steps_.forEach((step) => {
530532
let stepNode = goog.dom.getElement(prefix + step.id);
531533
step.node = stepNode;
532-
step.nodeContinue = stepNode.querySelector(
533-
'.js-project-step-continue');
534-
step.nodeHeader = stepNode.querySelector(
535-
'.js-project-step-header'),
536-
step.nodeListMediaExpand = stepNode.querySelectorAll(
537-
'.js-project-step-media-expand');
538-
step.nodeMessage = stepNode.querySelector('.'+prefix+'message');
534+
step.nodeContinue = stepNode.querySelector(classPrefix + 'continue');
535+
step.nodeLoadCode = stepNode.querySelector(classPrefix + 'load-code');
536+
step.nodeHeader = stepNode.querySelector(classPrefix + 'header');
537+
step.nodeListMediaExpand = stepNode.querySelectorAll(classPrefix +
538+
'media-expand');
539+
step.nodeMessage = stepNode.querySelector(classPrefix+'message');
539540
goog.style.setElementShown(step.nodeMessage, false);
540541
});
541542
this.initStepButtons_();
@@ -552,6 +553,10 @@ cwc.ui.Tutorial.prototype.initStepButtons_ = function() {
552553
goog.events.listen(step.nodeContinue, goog.events.EventType.CLICK,
553554
this.completeCurrentStep_.bind(this));
554555
}
556+
if (step.nodeLoadCode) {
557+
goog.events.listen(step.nodeLoadCode, goog.events.EventType.CLICK,
558+
this.loadCodeWithPrompt_.bind(this));
559+
}
555560
goog.events.listen(step.nodeHeader, goog.events.EventType.CLICK,
556561
this.jumpToStep_.bind(this, step.id));
557562

@@ -744,24 +749,83 @@ cwc.ui.Tutorial.prototype.showMedia_ = function(media) {
744749
* @private
745750
*/
746751
cwc.ui.Tutorial.prototype.setState_ = function(change) {
747-
let prevStepID = this.state_.activeStepID;
752+
let isEditorDirty = this.isEditorDirty_();
748753
Object.keys(change).forEach((key) => {
749754
this.state_[key] = change[key];
750755
});
751-
if (prevStepID !== this.state_.activeStepID) {
752-
let editorInstance = this.helper.getInstance('editor');
753-
let activeStep = this.getActiveStep_();
754-
if (editorInstance && activeStep.code) {
755-
this.solved(false);
756-
// TODO: support multiple editor views
757-
editorInstance.setEditorContent(activeStep.code,
758-
editorInstance.getCurrentView());
759-
}
760-
this.restartValidate_();
761-
}
762756
this.updateView_();
757+
if (!isEditorDirty) {
758+
this.loadCode_();
759+
}
763760
};
764761

762+
/**
763+
* Tests if the editor has been modified from the example code
764+
* @return {!boolean}
765+
* @private
766+
*/
767+
cwc.ui.Tutorial.prototype.isEditorDirty_ = function() {
768+
let editorInstance = this.helper.getInstance('editor');
769+
let activeStep = this.getActiveStep_();
770+
let code = editorInstance.getEditorContent(editorInstance.getCurrentView());
771+
code = code ? code.trim() : '';
772+
773+
// It's always ok to load code into an empty editor
774+
if (code.length === 0) {
775+
return false;
776+
}
777+
if (activeStep && activeStep.code && activeStep.code.trim() === code) {
778+
return false;
779+
}
780+
return true;
781+
};
782+
783+
/**
784+
* Prompts to overwrite dirty editor and loads code if user confirms
785+
* @private
786+
*/
787+
cwc.ui.Tutorial.prototype.loadCodeWithPrompt_ = function() {
788+
if (!this.getActiveStep_().code) {
789+
return;
790+
}
791+
if (!this.isEditorDirty_()) {
792+
this.loadCode_();
793+
return;
794+
}
795+
796+
let dialogInstance = this.helper.getInstance('dialog');
797+
let title = {
798+
icon: 'warning',
799+
title: 'Overwrite editor content?',
800+
};
801+
let content = 'Loading the example code will overwrite your changes in the ' +
802+
'editor. Are you sure you want to load the example code?';
803+
let action = i18t('Load example code into editor');
804+
dialogInstance.showActionCancel(title, content, action).then((answer) => {
805+
if (!answer) {
806+
return;
807+
}
808+
this.loadCode_();
809+
});
810+
};
811+
812+
/**
813+
* Loads example code into editor
814+
* @private
815+
*/
816+
cwc.ui.Tutorial.prototype.loadCode_ = function() {
817+
let activeStep = this.getActiveStep_();
818+
if (!(activeStep && activeStep.code)) {
819+
return;
820+
}
821+
let editorInstance = this.helper.getInstance('editor');
822+
// TODO: support multiple editor views
823+
editorInstance.setEditorContent(activeStep.code,
824+
editorInstance.getCurrentView());
825+
this.log_.info('Loaded example code into editor', activeStep.code);
826+
this.solved(false);
827+
this.restartValidate_();
828+
};
765829

766830
/**
767831
* Updates the view to reflect the current state

src/ui/tutorial/tutorial.soy

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,16 @@
7676
{@param id: int}
7777
{@param images: list<string>}
7878
{@param isLastStep: bool}
79+
{@param hasCode: bool}
7980
{@param number: number}
8081
{@param online: bool}
8182
{@param prefix: string}
8283
{@param title: string}
8384
{@param videos: list<string>}
8485
{@param youtube_videos: list<string>}
8586

86-
<li id="{$prefix}{$id}" class="{$prefix}container js-project-step">
87-
<div class="{$prefix}header js-project-step-header">
87+
<li id="{$prefix}{$id}" class="{$prefix}container {$prefix}step">
88+
<div class="{$prefix}header">
8889
<div class="{$prefix}number">
8990
<span class="{$prefix}number-text">{$number}</span>
9091
<span class="{$prefix}number-check">
@@ -98,33 +99,38 @@
9899
<div class="{$prefix}description">{$description}</div>
99100
<div class="{$prefix}media">
100101
{foreach $image in $images}
101-
<button type="button" class="{$prefix}media-expand js-project-step-media-expand" title="Expand image" data-media-type="image">
102-
<img data-src="{$image}" alt="" class="{$prefix}media-item js-project-step-image">
102+
<button type="button" class="{$prefix}media-expand {$prefix}media-expand" title="Expand image" data-media-type="image">
103+
<img data-src="{$image}" alt="" class="{$prefix}media-item {$prefix}image">
103104
<i class="material-icons">fullscreen</i>
104105
</button>
105106
{/foreach}
106107
{foreach $video in $videos}
107-
<button type="button" class="{$prefix}media-expand {$prefix}media-expand js-project-step-media-expand" title="Expand video" data-media-type="video" data-video-url="{$video}">
108+
<button type="button" class="{$prefix}media-expand {$prefix}media-expand {$prefix}media-expand" title="Expand video" data-media-type="video" data-video-url="{$video}">
108109
<div class="{$prefix}media-item-video">
109110
<i class="material-icons">play_circle_outline</i>
110111
</div>
111112
</button>
112113
{/foreach}
113114
{if $online}
114115
{foreach $video in $youtube_videos}
115-
<button type="button" class="{$prefix}media-expand {$prefix}media-expand js-project-step-media-expand" title="Expand video" data-media-type="youtube" data-youtube-id="{$video}">
116+
<button type="button" class="{$prefix}media-expand {$prefix}media-expand {$prefix}media-expand" title="Expand video" data-media-type="youtube" data-youtube-id="{$video}">
116117
<div class="{$prefix}media-item-video">
117118
<i class="material-icons">play_circle_outline</i>
118119
</div>
119120
</button>
120121
{/foreach}
121122
{/if}
122123
</div>
123-
{if not $isLastStep}
124124
<div class="{$prefix}actions">
125-
<button type="button" class="mdl-button mdl-js-button mdl-button--colored mdl-button--raised js-project-step-continue">Continue</button>
125+
{if $hasCode}
126+
<button type="button" class="mdl-button mdl-js-button mdl-button--colored mdl-button--raised {$prefix}load-code">
127+
<i class='material-icons'>chevron_left</i><label>Load Example Code</label>
128+
</button>
129+
{/if}
130+
{if not $isLastStep}
131+
<button type="button" class="mdl-button mdl-js-button mdl-button--colored mdl-button--raised {$prefix}continue">Continue</button>
132+
{/if}
126133
</div>
127-
{/if}
128134
</div>
129135
</li>
130136
{/template}

0 commit comments

Comments
 (0)