Commit 3a70ffb4 authored by Akihiro Ota's avatar Akihiro Ota Committed by Commit Bot

ChromeVox Tutorial: Unify hint and nudge system.

This change merges the implementation for the hint and nudge systems
in the interactive tutorial. Now, nudges and hints are both mangaged
by the tutorial component, instead of being split between the tutorial
and lesson components.

Bug: N/A
Change-Id: I68f237690f336ec22f2af97bf978e76340d6ecb5
AX-Relnotes: N/A
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2401945
Commit-Queue: Akihiro Ota <akihiroota@chromium.org>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#805936}
parent c96d2fb4
......@@ -127,7 +127,6 @@ h1 {
practice-instructions="[[ lesson.practiceInstructions ]]"
practice-file="[[ lesson.practiceFile ]]"
practice-state="[[ lesson.practiceState ]]"
hints="[[ lesson.hints ]]"
events="[[ lesson.events ]]"
actions="[[ lesson.actions ]]"
auto-interactive="[[ lesson.autoInteractive ]]"
......
......@@ -36,6 +36,21 @@ const Screen = {
LESSON: 'lesson',
};
/**
* The types of nudges given by the tutorial.
* General nudges: announce the current item three times, then give two general
* hints about how to navigate with ChromeVox, then a final nudge about how to
* exit the tutorial.
* Practice area nudges: specified by the |hints| array in lessonData. These
* are nudges for the practice area and are only given when the practice area
* is active.
* @enum {string}
*/
const NudgeType = {
GENERAL: 'general',
PRACTICE_AREA: 'practice_area'
};
Polymer({
is: 'i-tutorial',
......@@ -452,22 +467,18 @@ Polymer({
ready() {
document.addEventListener('keydown', this.onKeyDown.bind(this));
this.hideAllScreens();
this.initializeNudges();
this.$.lessonTemplate.addEventListener('dom-change', (evt) => {
// Executes once all lessons have been added to the dom.
this.show();
});
this.$.tutorial.addEventListener('focus', this.onFocus.bind(this), true);
this.addEventListener('startpractice', (evt) => {
// Stop nudges when the practice area opens. This is because the practice
// area can provide hints and we don't want nudges to conflict with them.
this.stopNudges();
this.isPracticeAreaActive = true;
this.startNudges(NudgeType.PRACTICE_AREA);
});
this.addEventListener('endpractice', (evt) => {
// When the practice area closes, it's safe to resume nudges.
this.startNudges();
this.isPracticeAreaActive = false;
this.startNudges(NudgeType.GENERAL);
});
},
......@@ -481,7 +492,7 @@ Polymer({
} else {
this.showMainMenu();
}
this.startNudges();
this.startNudges(NudgeType.GENERAL);
},
/**
......@@ -753,13 +764,64 @@ Polymer({
}
},
/** @private */
startNudges() {
// Nudges.
/**
* @param {NudgeType} type
* @private
*/
startNudges(type) {
this.stopNudges();
this.initializeNudges(type);
this.setNudgeInterval();
},
/** @private */
setNudgeInterval() {
this.nudgeIntervalId =
setInterval(this.giveNudge.bind(this), this.NUDGE_INTERVAL_TIME_MS);
},
/**
* @param {NudgeType} type
* @private
*/
initializeNudges(type) {
const maybeGiveNudge = (msg) => {
if (this.interactiveMode) {
// Do not announce message since ChromeVox blocks actions in interactive
// mode.
return;
}
this.requestSpeech(msg);
};
this.nudgeArray = [];
if (type === NudgeType.PRACTICE_AREA) {
// Convert hint strings into functions that will request speech for those
// strings.
const hints = this.lessonData[this.activeLessonNum].hints;
for (const hint of hints) {
this.nudgeArray.push(this.requestSpeech.bind(this, hint));
}
} else if (type === NudgeType.GENERAL) {
this.nudgeArray = [
this.requestFullyDescribe.bind(this),
this.requestFullyDescribe.bind(this),
this.requestFullyDescribe.bind(this),
maybeGiveNudge.bind(
this, 'Hint: Hold Search and press the arrow keys to navigate.'),
maybeGiveNudge.bind(
this, 'Hint: Press Search + Space to activate the current item.'),
this.requestSpeech.bind(
this, 'Hint: Press Escape if you would like to exit this tutorial.')
];
} else {
throw new Error('Invalid NudgeType: ' + type);
}
},
/** @private */
stopNudges() {
this.nudgeCounter = 0;
......@@ -768,6 +830,12 @@ Polymer({
}
},
/** @private */
restartNudges() {
this.stopNudges();
this.setNudgeInterval();
},
/** @private */
giveNudge() {
if (this.nudgeCounter < 0 || this.nudgeCounter >= this.nudgeArray.length) {
......@@ -794,42 +862,17 @@ Polymer({
new CustomEvent('requestfullydescribe', {composed: true}));
},
/** @private */
initializeNudges() {
const maybeGiveNudge = (msg) => {
if (this.interactiveMode) {
// Do not announce message since ChromeVox blocks actions in interactive
// mode.
return;
}
this.requestSpeech(msg);
};
this.nudgeArray = [
this.requestFullyDescribe.bind(this),
this.requestFullyDescribe.bind(this),
this.requestFullyDescribe.bind(this),
maybeGiveNudge.bind(
this, 'Hint: Hold Search and press the arrow keys to navigate.'),
maybeGiveNudge.bind(
this, 'Hint: Press Search + Space to activate the current item.'),
this.requestSpeech.bind(
this, 'Hint: Press Escape if you would like to exit this tutorial.')
];
},
/**
* @param {Event} evt
* @private
*/
onFocus(evt) {
// Restart nudges whenever focus changes, as long as the practice area is
// inactive.
// Restart nudges whenever focus changes. Skip this for the practice area
// so nudges are given in regular intervals.
if (this.isPracticeAreaActive) {
return;
}
this.startNudges();
this.restartNudges();
}
});
......@@ -33,12 +33,6 @@ export const TutorialLesson = Polymer({
events: {type: Array},
hints: {type: Array},
hintCounter: {type: Number, value: 0},
hintIntervalId: {type: Number},
goalStateReached: {type: Boolean, value: false},
actions: {type: Array},
......@@ -136,14 +130,12 @@ export const TutorialLesson = Polymer({
startPractice() {
this.notifyStartPractice();
this.$.practice.showModal();
this.startHints();
this.$.practiceTitle.focus();
},
/** @private */
endPractice() {
this.notifyEndPractice();
this.stopHints();
this.$.startPractice.focus();
},
......@@ -209,36 +201,12 @@ export const TutorialLesson = Polymer({
this.goalStateReached = true;
if (previousState === false) {
// Only perform when crossing the threshold from not reached to reached.
this.stopHints();
this.requestSpeech(
'You have passed this tutorial lesson. Find and press the exit ' +
'practice area button to continue');
}
},
// Methods for managing hints.
/** @private */
startHints() {
this.hintCounter = 0;
this.hintIntervalId = setInterval(() => {
if (this.hintCounter >= this.hints.length) {
this.stopHints();
return;
}
this.requestSpeech(this.hints[this.hintCounter]);
this.hintCounter += 1;
}, 20 * 1000);
},
/** @private */
stopHints() {
if (this.hintIntervalId) {
clearInterval(this.hintIntervalId);
}
},
// Miscellaneous methods.
/**
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment