Bug 1558417 - Add onboarding UI for Editor. r=Honza.

The onboarding UI is displayed on top of the Editor.
A dismiss button hides it forever (this is handled via
a pref that is set to false when clicking the button).

We take this as an opportunity to polish the Editor UI/wording
a bit.

Differential Revision: https://phabricator.services.mozilla.com/D43621

--HG--
rename : devtools/client/debugger/images/resume.svg => devtools/client/themes/images/webconsole/run.svg
extra : moz-landing-system : lando
This commit is contained in:
Nicolas Chevobbe 2019-08-28 11:47:49 +00:00
parent 1fab526cce
commit db62899801
17 changed files with 253 additions and 31 deletions

View File

@ -1789,7 +1789,7 @@ pref("signon.management.page.feedbackURL",
"https://www.surveygizmo.com/s3/5036102/Lockwise-feedback?ver=%VERSION%");
// The utm_creative value is appended within the code (specific to the location on
// where it is clicked). Be sure that if these two prefs are updated, that
// the utm_creative param be last.
// the utm_creative param be last.
pref("signon.management.page.mobileAndroidURL", "https://app.adjust.com/6tteyjo?redirect=https%3A%2F%2Fplay.google.com%2Fstore%2Fapps%2Fdetails%3Fid%3Dmozilla.lockbox&utm_campaign=Desktop&utm_adgroup=InProduct&utm_creative=");
pref("signon.management.page.mobileAppleURL", "https://app.adjust.com/6tteyjo?redirect=https%3A%2F%2Fitunes.apple.com%2Fapp%2Fid1314000270%3Fmt%3D8&utm_campaign=Desktop&utm_adgroup=InProduct&utm_creative=");
pref("signon.management.page.breachAlertUrl",
@ -2256,10 +2256,13 @@ pref("devtools.webconsole.timestampMessages", false);
// Saved editor mode state in the console.
pref("devtools.webconsole.input.editor", false);
// Editor width for webconsole and browserconsole
// Editor width for webconsole and browserconsole.
pref("devtools.webconsole.input.editorWidth", 0);
pref("devtools.browserconsole.input.editorWidth", 0);
// Display an onboarding UI for the Editor mode.
pref("devtools.webconsole.input.editorOnboarding", true);
// Disable the new performance recording panel by default
pref("devtools.performance.new-panel-enabled", false);

View File

@ -129,6 +129,7 @@ devtools.jar:
skin/images/webconsole/input.svg (themes/images/webconsole/input.svg)
skin/images/webconsole/navigation.svg (themes/images/webconsole/navigation.svg)
skin/images/webconsole/return.svg (themes/images/webconsole/return.svg)
skin/images/webconsole/run.svg (themes/images/webconsole/run.svg)
skin/images/breadcrumbs-scrollbutton.svg (themes/images/breadcrumbs-scrollbutton.svg)
skin/animation.css (themes/animation.css)
skin/perf.css (themes/perf.css)

View File

@ -428,17 +428,17 @@ webconsole.editor.toolbar.history.prevExpressionButton.tooltip=Previous Expressi
# which is displayed when the editor mode is enabled (devtools.webconsole.input.editor=true).
webconsole.editor.toolbar.history.nextExpressionButton.tooltip=Next Expression
# LOCALIZATION NOTE (webconsole.editor.toolbar.closeButton.tooltip)
# LOCALIZATION NOTE (webconsole.editor.toolbar.closeButton.tooltip2)
# Label used for the tooltip on the close button, in the editor toolbar, which is
# displayed when the editor mode is enabled (devtools.webconsole.input.editor=true).
# Parameters: %S is the keyboard shortcut.
webconsole.editor.toolbar.closeButton.tooltip=Close Editor (%S)
webconsole.editor.toolbar.closeButton.tooltip2=Switch back to inline mode (%S)
# LOCALIZATION NOTE (webconsole.input.openEditorButton.tooltip)
# LOCALIZATION NOTE (webconsole.input.openEditorButton.tooltip2)
# Label used for the tooltip on the open editor button, in console input, which is
# displayed when the console is in regular mode.
# Parameters: %S is the keyboard shortcut.
webconsole.input.openEditorButton.tooltip=Open Editor (%S)
webconsole.input.openEditorButton.tooltip2=Switch to multi-line editor mode (%S)
# LOCALIZATION NOTE (webconsole.warningGroup.messageCount.tooltip): the tooltip text
# displayed when you hover a warning group badge (i.e. repeated warning messages for a
@ -448,3 +448,19 @@ webconsole.input.openEditorButton.tooltip=Open Editor (%S)
# #1 number of message in the group.
# example: 3 messages
webconsole.warningGroup.messageCount.tooltip=#1 message;#1 messages
# LOCALIZATION NOTE (webconsole.input.editor.onboarding.label): the text that is displayed
# when displaying the multiline-input mode for the first time, until the user dismiss the
# text.
# Parameters: %1$S is Enter key, %2$S is the shorcut to evaluate the expression (
# Ctrl+Enter or Cmd+Enter on OSX).
webconsole.input.editor.onboarding.label=Iterate on your code faster with the new multi-line editor mode. Use %1$S to add new lines and %2$S to run.
# LOCALIZATION NOTE (webconsole.input.editor.onboarding.dissmis.label): the text that is
# displayed in the multiline-input mode onboarding UI to dismiss it.
webconsole.input.editor.onboarding.dissmis.label=Got it!
# LOCALIZATION NOTE (webconsole.enterKey): The text that will be used to represent the
# Enter key in the editor onboarding UI, as well as on the Editor toolbar "Run" button
# tooltip.
webconsole.enterKey=Enter

View File

@ -0,0 +1,6 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
<path fill="context-fill" d="M5 3v10l7-5-7-5zM4 3c0-.81.92-1.31 1.58-.84l7 5.03a1 1 0 0 1 0 1.62l-7 5.03C4.92 14.31 4 13.81 4 13V3z"/>
</svg>

After

Width:  |  Height:  |  Size: 441 B

View File

@ -22,6 +22,7 @@ const {
FILTERBAR_DISPLAY_MODE_SET,
EDITOR_TOGGLE,
EDITOR_SET_WIDTH,
EDITOR_ONBOARDING_DISMISS,
} = require("devtools/client/webconsole/constants");
function persistToggle() {
@ -97,6 +98,15 @@ function editorToggle() {
};
}
function editorOnboardingDismiss() {
return ({ dispatch, prefsService }) => {
dispatch({
type: EDITOR_ONBOARDING_DISMISS,
});
prefsService.setBoolPref(PREFS.UI.EDITOR_ONBOARDING, false);
};
}
function setEditorWidth(width) {
return ({ dispatch, prefsService }) => {
dispatch({
@ -151,6 +161,7 @@ function filterBarDisplayModeSet(displayMode) {
module.exports = {
contentMessagesToggle,
editorOnboardingDismiss,
editorToggle,
filterBarDisplayModeSet,
initialize,

View File

@ -231,11 +231,25 @@ body {
grid-column: 1 / 2;
grid-row: 2 / 3;
display: grid;
align-items: center;
/* We're going to have the run button, the history nav and the close button */
grid-template-columns: auto 1fr auto auto auto;
height: unset;
}
.jsterm-editor .webconsole-editor-toolbar .webconsole-editor-toolbar-executeButton {
background-image: url(chrome://devtools/skin/images/webconsole/run.svg);
-moz-context-properties: fill;
fill: currentColor;
padding-inline-start: 22px;
background-repeat: no-repeat;
background-position: 4px center;
padding-inline-end: 8px;
height: 20px;
margin-inline-start: 5px;
background-size: 16px 16px;
}
.jsterm-editor .webconsole-editor-toolbar .webconsole-editor-toolbar-history-prevExpressionButton {
grid-column: -3 / -4;
}
@ -279,7 +293,10 @@ body {
width: 30vw;
min-width: 150px;
border-top: none;
display: block;
padding: 0;
/* Needed as we might have the onboarding UI displayed */
display: flex;
flex-direction: column;
}
.jsterm-editor #webconsole-notificationbox {
@ -288,6 +305,7 @@ body {
}
.jsterm-editor .jsterm-input-container > .CodeMirror {
flex: 1;
padding-inline-start: 0;
background-image: none;
}
@ -307,3 +325,55 @@ body {
/* We want the splitter to cover the whole column (minus self-xss message) */
grid-row: 2 / -1;
}
.editor-onboarding {
display: none;
}
.jsterm-editor .editor-onboarding {
display: grid;
/**
* Here's the design we want:
*
* Icon Onboarding text
*
* Got it!
*
**/
grid-template-columns: 22px 1fr;
border-bottom: 1px solid var(--theme-splitter-color);
padding: 8px 16px;
background-color: var(--theme-selection-background-hover);
grid-gap: 0 14px;
font-family: system-ui, -apple-system, sans-serif;
font-size: 12px;
line-height: 1.5;
}
.editor-onboarding-fox {
width: 22px;
height: 22px;
align-self: center;
}
.jsterm-editor .editor-onboarding p {
padding: 0;
margin: 0;
}
.jsterm-editor .editor-onboarding .editor-onboarding-shortcut {
font-weight: bold;
}
.editor-onboarding-dismiss-button {
grid-row: 2 / 3;
grid-column: 2 / 3;
justify-self: end;
padding: 2px;
background: transparent;
border: none;
color: var(--theme-highlight-blue);
font-family: inherit;
cursor: pointer;
font-size: inherit;
}

View File

@ -36,6 +36,8 @@ class EditorToolbar extends Component {
return null;
}
const enterStr = l10n.getStr("webconsole.enterKey");
return dom.div(
{
className:
@ -46,7 +48,7 @@ class EditorToolbar extends Component {
className: "devtools-button webconsole-editor-toolbar-executeButton",
title: l10n.getFormatStr(
"webconsole.editor.toolbar.executeButton.tooltip",
[isMacOS ? "Cmd + Enter" : "Ctrl + Enter"]
[isMacOS ? `Cmd + ${enterStr}` : `Ctrl + ${enterStr}`]
),
onClick: () => dispatch(actions.evaluateExpression()),
},
@ -75,7 +77,7 @@ class EditorToolbar extends Component {
dom.button({
className: "devtools-button webconsole-editor-toolbar-closeButton",
title: l10n.getFormatStr(
"webconsole.editor.toolbar.closeButton.tooltip",
"webconsole.editor.toolbar.closeButton.tooltip2",
[isMacOS ? "Cmd + B" : "Ctrl + B"]
),
onClick: () => dispatch(actions.editorToggle()),

View File

@ -95,11 +95,14 @@ class JSTerm extends Component {
autocompleteData: PropTypes.object.isRequired,
// Toggle the editor mode.
editorToggle: PropTypes.func.isRequired,
// Dismiss the editor onboarding UI.
editorOnboardingDismiss: PropTypes.func.isRequired,
// Is the editor feature enabled
editorFeatureEnabled: PropTypes.bool,
// Is the input in editor mode.
editorMode: PropTypes.bool,
editorWidth: PropTypes.number,
showEditorOnboarding: PropTypes.bool,
autocomplete: PropTypes.bool,
};
}
@ -470,10 +473,10 @@ class JSTerm extends Component {
}
shouldComponentUpdate(nextProps) {
// XXX: For now, everything is handled in an imperative way and we
// only want React to do the initial rendering of the component.
// This should be modified when the actual refactoring will take place.
return false;
return (
this.props.showEditorOnboarding !== nextProps.showEditorOnboarding ||
this.props.editorMode !== nextProps.editorMode
);
}
/**
@ -1045,6 +1048,59 @@ class JSTerm extends Component {
this.webConsoleUI = null;
}
renderOpenEditorButton() {
if (!this.props.editorFeatureEnabled || this.props.editorMode) {
return null;
}
return dom.button({
className: "devtools-button webconsole-input-openEditorButton",
title: l10n.getFormatStr("webconsole.input.openEditorButton.tooltip2", [
isMacOS ? "Cmd + B" : "Ctrl + B",
]),
onClick: this.props.editorToggle,
});
}
renderEditorOnboarding() {
if (!this.props.editorFeatureEnabled || !this.props.showEditorOnboarding) {
return null;
}
// We deliberately use getStr, and not getFormatStr, because we want keyboard
// shortcuts to be wrapped in their own span.
const label = l10n.getStr("webconsole.input.editor.onboarding.label");
let [prefix, suffix] = label.split("%1$S");
suffix = suffix.split("%2$S");
const enterString = l10n.getStr("webconsole.enterKey");
return dom.header(
{ className: "editor-onboarding" },
dom.img({
className: "editor-onboarding-fox",
src: "chrome://devtools/skin/images/fox-smiling.svg",
}),
dom.p(
{},
prefix,
dom.span({ className: "editor-onboarding-shortcut" }, enterString),
suffix[0],
dom.span({ className: "editor-onboarding-shortcut" }, [
isMacOS ? `Cmd+${enterString}` : `Ctrl+${enterString}`,
]),
suffix[1]
),
dom.button(
{
className: "editor-onboarding-dismiss-button",
onClick: () => this.props.editorOnboardingDismiss(),
},
l10n.getStr("webconsole.input.editor.onboarding.dissmis.label")
)
);
}
render() {
if (
this.props.webConsoleUI.isBrowserConsole &&
@ -1053,17 +1109,6 @@ class JSTerm extends Component {
return null;
}
const openEditorButton = this.props.editorFeatureEnabled
? dom.button({
className: "devtools-button webconsole-input-openEditorButton",
title: l10n.getFormatStr(
"webconsole.input.openEditorButton.tooltip",
[isMacOS ? "Cmd + B" : "Ctrl + B"]
),
onClick: this.props.editorToggle,
})
: undefined;
return dom.div(
{
className: "jsterm-input-container devtools-input devtools-monospace",
@ -1075,7 +1120,8 @@ class JSTerm extends Component {
this.node = node;
},
},
openEditorButton
this.renderOpenEditorButton(),
this.renderEditorOnboarding()
);
}
}
@ -1087,6 +1133,7 @@ function mapStateToProps(state) {
history: getHistory(state),
getValueFromHistory: direction => getHistoryValue(state, direction),
autocompleteData: getAutocompleteState(state),
showEditorOnboarding: state.ui.showEditorOnboarding,
};
}
@ -1100,6 +1147,7 @@ function mapDispatchToProps(dispatch) {
evaluateExpression: expression =>
dispatch(actions.evaluateExpression(expression)),
editorToggle: () => dispatch(actions.editorToggle()),
editorOnboardingDismiss: () => dispatch(actions.editorOnboardingDismiss()),
};
}

View File

@ -13,6 +13,7 @@ const actionTypes = {
BATCH_ACTIONS: "BATCH_ACTIONS",
CLEAR_HISTORY: "CLEAR_HISTORY",
EDITOR_TOGGLE: "EDITOR_TOGGLE",
EDITOR_ONBOARDING_DISMISS: "EDITOR_ONBOARDING_DISMISS",
EVALUATE_EXPRESSION: "EVALUATE_EXPRESSION",
FILTER_TEXT_SET: "FILTER_TEXT_SET",
FILTER_TOGGLE: "FILTER_TOGGLE",
@ -76,6 +77,8 @@ const prefs = {
MESSAGE_TIMESTAMP: "devtools.webconsole.timestampMessages",
// Store the editor width.
EDITOR_WIDTH: "input.editorWidth",
// Show the Editor onboarding UI
EDITOR_ONBOARDING: "devtools.webconsole.input.editorOnboarding",
},
FEATURES: {
// We use the same pref to enable the sidebar on webconsole and browser console.

View File

@ -16,6 +16,7 @@ const {
TIMESTAMPS_TOGGLE,
FILTERBAR_DISPLAY_MODE_SET,
FILTERBAR_DISPLAY_MODES,
EDITOR_ONBOARDING_DISMISS,
EDITOR_TOGGLE,
EDITOR_SET_WIDTH,
} = require("devtools/client/webconsole/constants");
@ -38,6 +39,7 @@ const UiState = overrides =>
reverseSearchInitialValue: "",
editor: false,
editorWidth: null,
showEditorOnboarding: false,
filterBarDisplayMode: FILTERBAR_DISPLAY_MODES.WIDE,
},
overrides
@ -87,6 +89,11 @@ function ui(state = UiState(), action) {
...state,
editor: !state.editor,
};
case EDITOR_ONBOARDING_DISMISS:
return {
...state,
showEditorOnboarding: false,
};
case EDITOR_SET_WIDTH:
return {
...state,

View File

@ -77,6 +77,7 @@ function configureStore(webConsoleUI, options = {}) {
: true,
editor: getBoolPref(PREFS.UI.EDITOR),
editorWidth: getIntPref(PREFS.UI.EDITOR_WIDTH),
showEditorOnboarding: getBoolPref(PREFS.UI.EDITOR_ONBOARDING),
timestampsVisible: getBoolPref(PREFS.UI.MESSAGE_TIMESTAMP),
}),
};

View File

@ -238,6 +238,7 @@ skip-if = os != 'mac' # The tested ctrl+key shortcuts are OSX only
[browser_jsterm_editor_execute_selection.js]
[browser_jsterm_editor_execute.js]
[browser_jsterm_editor_gutter.js]
[browser_jsterm_editor_onboarding.js]
[browser_jsterm_editor_toggle_keyboard_shortcut.js]
[browser_jsterm_editor_resize.js]
[browser_jsterm_editor_toolbar.js]

View File

@ -20,9 +20,9 @@ add_task(async function() {
false,
"Editor is disabled when pref is set to false"
);
let openEditorButton = getInlineOpenEditorButton(hud);
const openEditorButton = getInlineOpenEditorButton(hud);
ok(openEditorButton, "button is rendered in the inline input");
let rect = openEditorButton.getBoundingClientRect();
const rect = openEditorButton.getBoundingClientRect();
ok(rect.width > 0 && rect.height > 0, "Button is visible");
await closeConsole();
@ -37,9 +37,7 @@ add_task(async function() {
true,
"Editor is enabled when pref is set to true"
);
openEditorButton = getInlineOpenEditorButton(hud);
rect = openEditorButton.getBoundingClientRect();
ok(rect.width === 0 && rect.height === 0, "Button is hidden in editor mode");
is(getInlineOpenEditorButton(hud), null, "Button is hidden in editor mode");
await toggleLayout(hud);
getInlineOpenEditorButton(hud).click();

View File

@ -0,0 +1,53 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that the onboarding UI is displayed when first displaying the editor mode, and
// that it can be permanentely dismissed.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1558417
"use strict";
const TEST_URI = "data:text/html;charset=utf-8,Test onboarding UI";
const EDITOR_FEATURE_PREF = "devtools.webconsole.features.editor";
const EDITOR_UI_PREF = "devtools.webconsole.input.editor";
const EDITOR_ONBOARDING_PREF = "devtools.webconsole.input.editorOnboarding";
add_task(async function() {
// Enable editor mode and force the onboarding pref to true so it's displayed.
await pushPref(EDITOR_FEATURE_PREF, true);
await pushPref(EDITOR_UI_PREF, true);
await pushPref(EDITOR_ONBOARDING_PREF, true);
let hud = await openNewTabAndConsole(TEST_URI);
info("Check that the onboarding UI is displayed");
const onboardingElement = getOnboardingEl(hud);
ok(onboardingElement, "The onboarding UI exists");
info("Check that the onboarding UI can be dismissed");
const dismissButton = onboardingElement.querySelector(
".editor-onboarding-dismiss-button"
);
ok(dismissButton, "There's a dismiss button");
dismissButton.click();
await waitFor(() => !getOnboardingEl(hud));
ok(true, "The onboarding UI is hidden after clicking the dismiss button");
info("Check that the onboarding UI isn't displayed after a toolbox restart");
await closeConsole();
hud = await openConsole();
is(
getOnboardingEl(hud),
null,
"The onboarding UI isn't displayed after a toolbox restart after being dismissed"
);
Services.prefs.clearUserPref(EDITOR_FEATURE_PREF);
Services.prefs.clearUserPref(EDITOR_UI_PREF);
Services.prefs.clearUserPref(EDITOR_ONBOARDING_PREF);
});
function getOnboardingEl(hud) {
return hud.ui.outputNode.querySelector(".editor-onboarding");
}

View File

@ -11,6 +11,7 @@ const TEST_URI =
add_task(async function() {
await pushPref("devtools.webconsole.features.editor", true);
await pushPref("devtools.webconsole.input.editor", true);
await pushPref("devtools.webconsole.input.editorOnboarding", false);
// Reset editorWidth pref so we have steady results when running multiple times.
await pushPref("devtools.webconsole.input.editorWidth", null);

View File

@ -1,7 +1,7 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that hitting Ctrl + E does toggle the editor mode.
// Test that hitting Ctrl + B does toggle the editor mode.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1519105
"use strict";

View File

@ -31,6 +31,7 @@ pref("devtools.webconsole.input.autocomplete", true);
pref("devtools.browserconsole.contentMessages", true);
pref("devtools.webconsole.features.editor", true);
pref("devtools.webconsole.input.editorWidth", 800);
pref("devtools.webconsole.input.editorOnboarding", true);
global.loader = {
lazyServiceGetter: () => {},