Commit e7a877cd authored by Dave Schuyler's avatar Dave Schuyler Committed by Commit Bot

[MD extensions] shortcuts instructions and feedback

This CL provides a line of instructions for creating extensions shortcuts.
It also adds interactive feedback during shortcut entry.

Bug: 769576
Cq-Include-Trybots: master.tryserver.chromium.linux:closure_compilation
Change-Id: I419cb1f4e3ee63aa47fc56ca784faa5ec9f2fdb6
Reviewed-on: https://chromium-review.googlesource.com/748082
Commit-Queue: Dave Schuyler <dschuyler@chromium.org>
Reviewed-by: default avatarDemetrios Papadopoulos <dpapad@chromium.org>
Cr-Commit-Position: refs/heads/master@{#513256}
parent c1bfc6f9
...@@ -199,6 +199,41 @@ ...@@ -199,6 +199,41 @@
<message name="IDS_MD_EXTENSIONS_TYPE_A_SHORTCUT" desc="The prompt to the user to enter a keyboard shortcut in order to assign it to an extension."> <message name="IDS_MD_EXTENSIONS_TYPE_A_SHORTCUT" desc="The prompt to the user to enter a keyboard shortcut in order to assign it to an extension.">
Type a shortcut Type a shortcut
</message> </message>
<if expr="is_macosx">
<message name="IDS_MD_EXTENSIONS_SHORTCUT_INSTRUCTIONS" desc="Instructions explaining that shortcuts must start with either the Control, Alt, or Command key.">
Shortcuts must start with Ctrl, Alt, or Command
</message>
<message name="IDS_MD_EXTENSIONS_INCLUDE_START_MODIFIER" desc="Error message explaining that shortcuts must start with either the Control, Alt, or Command key.">
Include Ctrl, Alt, or Command
</message>
</if>
<if expr="chromeos">
<message name="IDS_MD_EXTENSIONS_SHORTCUT_INSTRUCTIONS" desc="Instructions explaining that shortcuts must start with either the Control, Alt, or Search key.">
Shortcuts must start with Ctrl, Alt, or Search
</message>
<message name="IDS_MD_EXTENSIONS_INCLUDE_START_MODIFIER" desc="Error message explaining that shortcuts must start with either the Control, Alt, or Search key.">
Include Ctrl, Alt, or Search
</message>
</if>
<if expr="is_macosx or chromeos">
<message name="IDS_MD_EXTENSIONS_TOO_MANY_MODIFIERS" desc="Error message explaining not to use so many modifiers in a shortcut.">
Invalid combination
</message>
</if>
<if expr="not is_macosx and not chromeos">
<message name="IDS_MD_EXTENSIONS_SHORTCUT_INSTRUCTIONS" desc="Instructions explaining that shortcuts must start with either the Control key or the Alt key.">
Shortcuts must start with either Ctrl or Alt
</message>
<message name="IDS_MD_EXTENSIONS_INCLUDE_START_MODIFIER" desc="Error message explaining that shortcuts must start with either the Control key or the Alt key.">
Include either Ctrl or Alt
</message>
<message name="IDS_MD_EXTENSIONS_TOO_MANY_MODIFIERS" desc="Error message explaining not to use both Ctrl and Alt in a shortcut.">
Either, not both Ctrl and Alt
</message>
</if>
<message name="IDS_MD_EXTENSIONS_NEED_CHARACTER" desc="Error message explaining that a shortcut needs a character. This is only shown if a valid modifier is already entered.">
Need a character
</message>
<if expr="chromeos"> <if expr="chromeos">
<!-- Extensions Kiosk apps --> <!-- Extensions Kiosk apps -->
<message name="IDS_MD_EXTENSIONS_MANAGE_KIOSK_APP" desc="Label of the button to bring up kiosk management overlay on chrome extensions page."> <message name="IDS_MD_EXTENSIONS_MANAGE_KIOSK_APP" desc="Label of the button to bring up kiosk management overlay on chrome extensions page.">
......
...@@ -14,24 +14,34 @@ ...@@ -14,24 +14,34 @@
<template> <template>
<style include="md-select cr-shared-style"> <style include="md-select cr-shared-style">
:host { :host {
--card-max-width: 928px;
--card-min-width: 600px;
height: 100%; height: 100%;
} }
#container { #instructions,
height: 100%; .shortcut-card {
overflow: overlay; margin: 0 auto 16px auto;
padding-top: 24px; max-width: var(--card-max-width);
min-width: var(--card-min-width);
width: 90%;
}
#instructions {
@apply --cr-title-text;
} }
.shortcut-card { .shortcut-card {
@apply(--cr-primary-text); @apply --cr-primary-text;
@apply(--shadow-elevation-2dp); @apply --shadow-elevation-2dp;
background-color: white; background-color: white;
margin: 0 auto 16px auto;
max-width: 928px;
min-width: 600px;
padding-bottom: 8px; padding-bottom: 8px;
width: 90%; }
#container {
height: 100%;
overflow: overlay;
padding-top: 24px;
} }
.command-entry { .command-entry {
...@@ -65,7 +75,7 @@ ...@@ -65,7 +75,7 @@
display: flex; display: flex;
margin-bottom: 9px; margin-bottom: 9px;
padding: 16px var(--cr-section-padding); padding: 16px var(--cr-section-padding);
@apply(--cr-title-text); @apply --cr-title-text;
} }
.icon { .icon {
...@@ -82,6 +92,7 @@ ...@@ -82,6 +92,7 @@
} }
</style> </style>
<div id="container"> <div id="container">
<div id="instructions">$i18n{shortcutInstructions}</div>
<template is="dom-repeat" items="[[calculateShownItems_(items.*)]]"> <template is="dom-repeat" items="[[calculateShownItems_(items.*)]]">
<div class="shortcut-card"> <div class="shortcut-card">
<div class="card-title"> <div class="card-title">
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
margin-bottom: 0px; margin-bottom: 0px;
margin-top: 2px; /* Offset underline spacing. */ margin-top: 2px; /* Offset underline spacing. */
padding: 0; padding: 0;
@apply(--cr-primary-text); @apply --cr-primary-text;
}; };
} }
...@@ -38,8 +38,12 @@ ...@@ -38,8 +38,12 @@
</style> </style>
<div id="main"> <div id="main">
<paper-input id="input" placeholder="$i18n{shortcutTypeAShortcut}" <paper-input id="input" placeholder="$i18n{shortcutTypeAShortcut}"
value="[[computeText_(capturing_, shortcut, pendingShortcut_)]]" error-message="[[getErrorString_(error_,
no-label-float> '$i18nPolymer{shortcutIncludeStartModifier}',
'$i18nPolymer{shortcutTooManyModifiers}',
'$i18nPolymer{shortcutNeedCharacter}')]]"
value="[[computeText_(capturing_, shortcut, pendingShortcut_)]]"
no-label-float>
</paper-input> </paper-input>
<button id="clear" is="paper-icon-button-light" <button id="clear" is="paper-icon-button-light"
class="icon-clear no-overlap" on-tap="onClearTap_" class="icon-clear no-overlap" on-tap="onClearTap_"
......
...@@ -5,6 +5,14 @@ ...@@ -5,6 +5,14 @@
cr.define('extensions', function() { cr.define('extensions', function() {
'use strict'; 'use strict';
/** @enum {number} */
const ShortcutError = {
NO_ERROR: 0,
INCLUDE_START_MODIFIER: 1,
TOO_MANY_MODIFIERS: 2,
NEED_CHARACTER: 3,
};
// The UI to display and manage keyboard shortcuts set for extension commands. // The UI to display and manage keyboard shortcuts set for extension commands.
const ShortcutInput = Polymer({ const ShortcutInput = Polymer({
is: 'extensions-shortcut-input', is: 'extensions-shortcut-input',
...@@ -36,6 +44,12 @@ cr.define('extensions', function() { ...@@ -36,6 +44,12 @@ cr.define('extensions', function() {
value: false, value: false,
}, },
/** @private {!ShortcutError} */
error_: {
type: Number,
value: 0,
},
/** @private */ /** @private */
pendingShortcut_: { pendingShortcut_: {
type: String, type: String,
...@@ -67,7 +81,9 @@ cr.define('extensions', function() { ...@@ -67,7 +81,9 @@ cr.define('extensions', function() {
return; return;
this.pendingShortcut_ = ''; this.pendingShortcut_ = '';
this.capturing_ = false; this.capturing_ = false;
this.$['input'].blur(); const input = this.$.input;
input.blur();
input.invalid = false;
this.delegate.setShortcutHandlingSuspended(false); this.delegate.setShortcutHandlingSuspended(false);
}, },
...@@ -109,6 +125,23 @@ cr.define('extensions', function() { ...@@ -109,6 +125,23 @@ cr.define('extensions', function() {
this.handleKey_(e); this.handleKey_(e);
}, },
/**
* @param {!ShortcutError} error
* @param {string} includeStartModifier
* @param {string} tooManyModifiers
* @param {string} needCharacter
* @return {string} UI string.
* @private
*/
getErrorString_: function(
error, includeStartModifier, tooManyModifiers, needCharacter) {
if (error == ShortcutError.TOO_MANY_MODIFIERS)
return tooManyModifiers;
if (error == ShortcutError.NEED_CHARACTER)
return needCharacter;
return includeStartModifier;
},
/** /**
* @param {!KeyboardEvent} e * @param {!KeyboardEvent} e
* @private * @private
...@@ -123,17 +156,27 @@ cr.define('extensions', function() { ...@@ -123,17 +156,27 @@ cr.define('extensions', function() {
// We don't allow both Ctrl and Alt in the same keybinding. // We don't allow both Ctrl and Alt in the same keybinding.
// TODO(devlin): This really should go in extensions.hasValidModifiers, // TODO(devlin): This really should go in extensions.hasValidModifiers,
// but that requires updating the existing page as well. // but that requires updating the existing page as well.
if ((e.ctrlKey && e.altKey) || !extensions.hasValidModifiers(e)) { if (e.ctrlKey && e.altKey) {
this.pendingShortcut_ = 'invalid'; this.error_ = ShortcutError.TOO_MANY_MODIFIERS;
this.$.input.invalid = true;
return;
}
if (!extensions.hasValidModifiers(e)) {
this.pendingShortcut_ = '';
this.error_ = ShortcutError.INCLUDE_START_MODIFIER;
this.$.input.invalid = true;
return; return;
} }
this.pendingShortcut_ = extensions.keystrokeToString(e); this.pendingShortcut_ = extensions.keystrokeToString(e);
if (!extensions.isValidKeyCode(e.keyCode)) {
if (extensions.isValidKeyCode(e.keyCode)) { this.error_ = ShortcutError.NEED_CHARACTER;
this.commitPending_(); this.$.input.invalid = true;
this.endCapture_(); return;
} }
this.$.input.invalid = false;
this.commitPending_();
this.endCapture_();
}, },
/** @private */ /** @private */
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include "chrome/browser/ui/webui/extensions/extensions_ui.h" #include "chrome/browser/ui/webui/extensions/extensions_ui.h"
#include <memory> #include <memory>
#include <utility>
#include "base/memory/ptr_util.h" #include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h" #include "base/metrics/histogram_macros.h"
...@@ -257,6 +258,14 @@ content::WebUIDataSource* CreateMdExtensionsSource() { ...@@ -257,6 +258,14 @@ content::WebUIDataSource* CreateMdExtensionsSource() {
IDS_MD_EXTENSIONS_SHORTCUT_SCOPE_IN_CHROME); IDS_MD_EXTENSIONS_SHORTCUT_SCOPE_IN_CHROME);
source->AddLocalizedString("shortcutTypeAShortcut", source->AddLocalizedString("shortcutTypeAShortcut",
IDS_MD_EXTENSIONS_TYPE_A_SHORTCUT); IDS_MD_EXTENSIONS_TYPE_A_SHORTCUT);
source->AddLocalizedString("shortcutInstructions",
IDS_MD_EXTENSIONS_SHORTCUT_INSTRUCTIONS);
source->AddLocalizedString("shortcutIncludeStartModifier",
IDS_MD_EXTENSIONS_INCLUDE_START_MODIFIER);
source->AddLocalizedString("shortcutTooManyModifiers",
IDS_MD_EXTENSIONS_TOO_MANY_MODIFIERS);
source->AddLocalizedString("shortcutNeedCharacter",
IDS_MD_EXTENSIONS_NEED_CHARACTER);
source->AddString( source->AddString(
"suspiciousInstallHelpUrl", "suspiciousInstallHelpUrl",
base::ASCIIToUTF16(google_util::AppendGoogleLocaleParam( base::ASCIIToUTF16(google_util::AppendGoogleLocaleParam(
......
...@@ -26,7 +26,7 @@ cr.define('extension_shortcut_input_tests', function() { ...@@ -26,7 +26,7 @@ cr.define('extension_shortcut_input_tests', function() {
} }
/** @enum {string} */ /** @enum {string} */
var TestNames = { const TestNames = {
Basic: 'basic', Basic: 'basic',
}; };
...@@ -44,12 +44,9 @@ cr.define('extension_shortcut_input_tests', function() { ...@@ -44,12 +44,9 @@ cr.define('extension_shortcut_input_tests', function() {
}); });
test(assert(TestNames.Basic), function() { test(assert(TestNames.Basic), function() {
var field = input.$['input']; const field = input.$['input'];
var fieldText = function() { expectEquals('', field.value);
return field.value; const isClearVisible =
};
expectEquals('', fieldText());
var isClearVisible =
extension_test_util.isVisible.bind(null, input, '#clear', false); extension_test_util.isVisible.bind(null, input, '#clear', false);
expectFalse(isClearVisible()); expectFalse(isClearVisible());
...@@ -60,24 +57,36 @@ cr.define('extension_shortcut_input_tests', function() { ...@@ -60,24 +57,36 @@ cr.define('extension_shortcut_input_tests', function() {
assertTrue(arg); assertTrue(arg);
input.delegate.reset(); input.delegate.reset();
expectEquals('', fieldText()); expectEquals('', field.value);
expectFalse(isClearVisible()); expectFalse(isClearVisible());
// Press character.
MockInteractions.keyDownOn(field, 'A', []);
expectEquals('', field.value);
expectTrue(field.errorMessage.startsWith('Include'));
// Add shift to character.
MockInteractions.keyDownOn(field, 'A', ['shift']);
expectEquals('', field.value);
expectTrue(field.errorMessage.startsWith('Include'));
// Press ctrl. // Press ctrl.
MockInteractions.keyDownOn(field, 17, ['ctrl']); MockInteractions.keyDownOn(field, 17, ['ctrl']);
expectEquals('Ctrl', fieldText()); expectEquals('Ctrl', field.value);
expectEquals('Need a character', field.errorMessage);
// Add shift. // Add shift.
MockInteractions.keyDownOn(field, 16, ['ctrl', 'shift']); MockInteractions.keyDownOn(field, 16, ['ctrl', 'shift']);
expectEquals('Ctrl + Shift', fieldText()); expectEquals('Ctrl + Shift', field.value);
expectEquals('Need a character', field.errorMessage);
// Remove shift. // Remove shift.
MockInteractions.keyUpOn(field, 16, ['ctrl']); MockInteractions.keyUpOn(field, 16, ['ctrl']);
expectEquals('Ctrl', fieldText()); expectEquals('Ctrl', field.value);
expectEquals('Need a character', field.errorMessage);
// Add alt (ctrl + alt is invalid). // Add alt (ctrl + alt is invalid).
MockInteractions.keyDownOn(field, 18, ['ctrl', 'alt']); MockInteractions.keyDownOn(field, 18, ['ctrl', 'alt']);
expectEquals('invalid', fieldText()); expectEquals('Ctrl', field.value);
// Remove alt. // Remove alt.
MockInteractions.keyUpOn(field, 18, ['ctrl']); MockInteractions.keyUpOn(field, 18, ['ctrl']);
expectEquals('Ctrl', fieldText()); expectEquals('Ctrl', field.value);
expectEquals('Need a character', field.errorMessage);
// Add 'A'. Once a valid shortcut is typed (like Ctrl + A), it is // Add 'A'. Once a valid shortcut is typed (like Ctrl + A), it is
// committed. // committed.
...@@ -87,7 +96,7 @@ cr.define('extension_shortcut_input_tests', function() { ...@@ -87,7 +96,7 @@ cr.define('extension_shortcut_input_tests', function() {
.then((arg) => { .then((arg) => {
input.delegate.reset(); input.delegate.reset();
expectDeepEquals(['itemid', 'Command', 'Ctrl+A'], arg); expectDeepEquals(['itemid', 'Command', 'Ctrl+A'], arg);
expectEquals('Ctrl + A', fieldText()); expectEquals('Ctrl + A', field.value);
expectEquals('Ctrl+A', input.shortcut); expectEquals('Ctrl+A', input.shortcut);
expectTrue(isClearVisible()); expectTrue(isClearVisible());
......
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