Bug 1404801 - Part 5: Show error message in case of no animations. r?gl draft
authorDaisuke Akatsuka <dakatsuka@mozilla.com>
Thu, 26 Oct 2017 16:59:48 +0900
changeset 686839 0cb66757e3c2cdde404304debe7ee90ba2754d68
parent 686838 ff64a25f322390cdea6824a8cb79ab5bc40db256
child 686840 da4b024afe09ed81b19b8ba2703277b1a8e174ae
push id86313
push userbmo:dakatsuka@mozilla.com
push dateThu, 26 Oct 2017 14:09:43 +0000
reviewersgl
bugs1404801
milestone58.0a1
Bug 1404801 - Part 5: Show error message in case of no animations. r?gl MozReview-Commit-ID: 538oUjKMsnT
devtools/client/inspector/animation/actions/element-picker.js
devtools/client/inspector/animation/actions/index.js
devtools/client/inspector/animation/actions/moz.build
devtools/client/inspector/animation/animation.js
devtools/client/inspector/animation/components/App.js
devtools/client/inspector/animation/components/NoAnimationPanel.js
devtools/client/inspector/animation/components/moz.build
devtools/client/inspector/animation/moz.build
devtools/client/inspector/animation/reducers/element-picker.js
devtools/client/inspector/animation/reducers/moz.build
devtools/client/inspector/animation/utils/moz.build
devtools/client/inspector/animation/utils/utils.js
devtools/client/inspector/reducers.js
devtools/client/locales/en-US/animationinspector.properties
devtools/client/themes/animation.css
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/actions/element-picker.js
@@ -0,0 +1,19 @@
+/* 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/. */
+
+"use strict";
+
+const { UPDATE_ELEMENT_PICKER_ENABLED } = require("./index");
+
+module.exports = {
+  /**
+   * Update the state of element picker in animation inspector.
+   */
+  updateElementPickerEnabled(isEnabled) {
+    return {
+      type: UPDATE_ELEMENT_PICKER_ENABLED,
+      isEnabled,
+    };
+  }
+};
--- a/devtools/client/inspector/animation/actions/index.js
+++ b/devtools/client/inspector/animation/actions/index.js
@@ -4,9 +4,11 @@
 
 "use strict";
 
 const { createEnum } = require("devtools/client/shared/enum");
 
 createEnum([
   // Update the list of animation.
   "UPDATE_ANIMATIONS",
+  // Update state of the picker enabled.
+  "UPDATE_ELEMENT_PICKER_ENABLED",
 ], module.exports);
--- a/devtools/client/inspector/animation/actions/moz.build
+++ b/devtools/client/inspector/animation/actions/moz.build
@@ -1,8 +1,9 @@
 # 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/.
 
 DevToolsModules(
     'animations.js',
+    'element-picker.js',
     'index.js'
 )
--- a/devtools/client/inspector/animation/animation.js
+++ b/devtools/client/inspector/animation/animation.js
@@ -4,52 +4,95 @@
 
 "use strict";
 
 const { AnimationsFront } = require("devtools/shared/fronts/animation");
 const { createElement, createFactory } = require("devtools/client/shared/vendor/react");
 const { Provider } = require("devtools/client/shared/vendor/react-redux");
 
 const App = createFactory(require("./components/App"));
+const { isAllTimingEffectEqual } = require("./utils/utils");
 const { updateAnimations } = require("./actions/animations");
+const { updateElementPickerEnabled } = require("./actions/element-picker");
 
 class AnimationInspector {
   constructor(inspector) {
     this.inspector = inspector;
+
+    this.toggleElementPicker = this.toggleElementPicker.bind(this);
     this.update = this.update.bind(this);
+    this.onElementPickerStarted = this.onElementPickerStarted.bind(this);
+    this.onElementPickerStopped = this.onElementPickerStopped.bind(this);
 
     this.init();
   }
 
   init() {
     const target = this.inspector.target;
     this.animationsFront = new AnimationsFront(target.client, target.form);
 
-    const provider = createElement(Provider, {
-      id: "newanimationinspector",
-      key: "newanimationinspector",
-      store: this.inspector.store
-    }, App());
+    const provider = createElement(Provider,
+      {
+        id: "newanimationinspector",
+        key: "newanimationinspector",
+        store: this.inspector.store
+      },
+      App(
+        {
+          toggleElementPicker: this.toggleElementPicker
+        }
+      )
+    );
     this.provider = provider;
 
     this.inspector.selection.on("new-node-front", this.update);
     this.inspector.sidebar.on("newanimationinspector-selected", this.update);
+    this.inspector.toolbox.on("picker-started", this.onElementPickerStarted);
+    this.inspector.toolbox.on("picker-stopped", this.onElementPickerStopped);
   }
 
   destroy() {
     this.inspector.selection.off("new-node-front", this.update);
     this.inspector.sidebar.off("newanimationinspector-selected", this.update);
+    this.inspector.toolbox.off("picker-started", this.onElementPickerStarted);
+    this.inspector.toolbox.off("picker-stopped", this.onElementPickerStopped);
 
     this.inspector = null;
   }
 
   async update() {
+    if (!this.inspector || !this.isPanelVisible()) {
+      // AnimationInspector was destroyed already or the panel is hidden.
+      return;
+    }
+
     const selection = this.inspector.selection;
     const animations =
       selection.isConnected() && selection.isElementNode()
       ? await this.animationsFront.getAnimationPlayersForNode(selection.nodeFront)
       : [];
 
-    this.inspector.store.dispatch(updateAnimations(animations));
+    if (!this.animations || !isAllTimingEffectEqual(animations, this.animations)) {
+      this.inspector.store.dispatch(updateAnimations(animations));
+      this.animations = animations;
+    }
+  }
+
+  isPanelVisible() {
+    return this.inspector && this.inspector.toolbox && this.inspector.sidebar &&
+           this.inspector.toolbox.currentToolId === "inspector" &&
+           this.inspector.sidebar.getCurrentTabID() === "newanimationinspector";
+  }
+
+  toggleElementPicker() {
+    this.inspector.toolbox.highlighterUtils.togglePicker();
+  }
+
+  onElementPickerStarted() {
+    this.inspector.store.dispatch(updateElementPickerEnabled(true));
+  }
+
+  onElementPickerStopped() {
+    this.inspector.store.dispatch(updateElementPickerEnabled(false));
   }
 }
 
 module.exports = AnimationInspector;
--- a/devtools/client/inspector/animation/components/App.js
+++ b/devtools/client/inspector/animation/components/App.js
@@ -4,37 +4,45 @@
 
 "use strict";
 
 const { createFactory, DOM: dom, PropTypes, PureComponent } =
   require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 
 const AnimationList = createFactory(require("./AnimationList"));
+const NoAnimationPanel = createFactory(require("./NoAnimationPanel"));
 
 class App extends PureComponent {
   static get propTypes() {
     return {
       animations: PropTypes.arrayOf(PropTypes.object).isRequired,
+      toggleElementPicker: PropTypes.func.isRequired,
     };
   }
 
   shouldComponentUpdate(nextProps, nextState) {
     return this.props.animations.length !== 0 || nextProps.animations.length !== 0;
   }
 
   render() {
-    const { animations } = this.props;
+    const { animations, toggleElementPicker } = this.props;
 
     return dom.div(
       {
         id: "animation-container"
       },
-      AnimationList(
+      animations.length
+      ? AnimationList(
         {
           animations
         }
       )
+      : NoAnimationPanel(
+        {
+          toggleElementPicker
+        }
+      )
     );
   }
 }
 
 module.exports = connect(state => state)(App);
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/components/NoAnimationPanel.js
@@ -0,0 +1,56 @@
+/* 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/. */
+
+"use strict";
+
+const { DOM: dom, PropTypes, PureComponent } =
+  require("devtools/client/shared/vendor/react");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+const { LocalizationHelper } = require("devtools/shared/l10n");
+
+const L10N =
+  new LocalizationHelper("devtools/client/locales/animationinspector.properties");
+
+class NoAnimationPanel extends PureComponent {
+  static get propTypes() {
+    return {
+      elementPicker: PropTypes.object.isRequired,
+      toggleElementPicker: PropTypes.func.isRequired,
+    };
+  }
+
+  shouldComponentUpdate(nextProps, nextState) {
+    return this.props.elementPicker.isEnabled != nextProps.elementPicker.isEnabled;
+  }
+
+  render() {
+    const { elementPicker, toggleElementPicker } = this.props;
+
+    return dom.div(
+      {
+        className: "animation-error-message devtools-sidepanel-no-result"
+      },
+      dom.p(
+        null,
+        L10N.getStr("panel.noAnimation")
+      ),
+      dom.button(
+        {
+          className: "animation-element-picker devtools-button"
+                     + (elementPicker.isEnabled ? " checked" : ""),
+          "data-standalone": true,
+          onClick: toggleElementPicker
+        }
+      )
+    );
+  }
+}
+
+const mapStateToProps = state => {
+  return {
+    elementPicker: state.animationElementPicker
+  };
+};
+
+module.exports = connect(mapStateToProps)(NoAnimationPanel);
--- a/devtools/client/inspector/animation/components/moz.build
+++ b/devtools/client/inspector/animation/components/moz.build
@@ -1,9 +1,10 @@
 # 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/.
 
 DevToolsModules(
     'AnimationItem.js',
     'AnimationList.js',
-    'App.js'
+    'App.js',
+    'NoAnimationPanel.js'
 )
--- a/devtools/client/inspector/animation/moz.build
+++ b/devtools/client/inspector/animation/moz.build
@@ -1,15 +1,16 @@
 # 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/.
 
 DIRS += [
     'actions',
     'components',
-    'reducers'
+    'reducers',
+    'utils'
 ]
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
 
 DevToolsModules(
     'animation.js'
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/reducers/element-picker.js
@@ -0,0 +1,22 @@
+/* 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/. */
+
+"use strict";
+
+const { UPDATE_ELEMENT_PICKER_ENABLED } = require("../actions/index");
+
+const INITIAL_STATE = { isEnabled: false };
+
+const reducers = {
+  [UPDATE_ELEMENT_PICKER_ENABLED](state, { isEnabled }) {
+    return Object.assign({}, state, {
+      isEnabled
+    });
+  }
+};
+
+module.exports = function (state = INITIAL_STATE, action) {
+  const reducer = reducers[action.type];
+  return reducer ? reducer(state, action) : state;
+};
--- a/devtools/client/inspector/animation/reducers/moz.build
+++ b/devtools/client/inspector/animation/reducers/moz.build
@@ -1,7 +1,8 @@
 # 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/.
 
 DevToolsModules(
-    'animations.js'
+    'animations.js',
+    'element-picker.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/utils/moz.build
@@ -0,0 +1,7 @@
+# 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/.
+
+DevToolsModules(
+    'utils.js'
+)
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/utils/utils.js
@@ -0,0 +1,46 @@
+/* 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/. */
+
+"use strict";
+
+/**
+ * Check the equality timing effects from given animations.
+ *
+ * @param {Array} animations.
+ * @param {Array} same to avobe.
+ * @return {Boolean} true: same timing effects
+ */
+function isAllTimingEffectEqual(animationsA, animationsB) {
+  if (animationsA.length !== animationsB.length) {
+    return false;
+  }
+
+  for (let i = 0; i < animationsA.length; i++) {
+    if (!isTimingEffectEqual(animationsA[i].state, animationsB[i].state)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+/**
+ * Check the equality given states as effect timing.
+ *
+ * @param {Object} state of animation.
+ * @param {Object} same to avobe.
+ * @return {Boolean} true: same effect timing
+ */
+function isTimingEffectEqual(stateA, stateB) {
+  return stateA.delay === stateB.delay &&
+         stateA.direction === stateB.direction &&
+         stateA.duration === stateB.duration &&
+         stateA.easing === stateB.easing &&
+         stateA.endDelay === stateB.endDelay &&
+         stateA.fill === stateB.fill &&
+         stateA.iterationCount === stateB.iterationCount &&
+         stateA.iterationStart === stateB.iterationStart;
+}
+
+module.exports.isAllTimingEffectEqual = isAllTimingEffectEqual;
+module.exports.isTimingEffectEqual = isTimingEffectEqual;
--- a/devtools/client/inspector/reducers.js
+++ b/devtools/client/inspector/reducers.js
@@ -3,14 +3,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 // This file exposes the Redux reducers of the box model, grid and grid highlighter
 // settings.
 
 exports.animations = require("devtools/client/inspector/animation/reducers/animations");
+exports.animationElementPicker =
+  require("devtools/client/inspector/animation/reducers/element-picker");
 exports.boxModel = require("devtools/client/inspector/boxmodel/reducers/box-model");
 exports.extensionsSidebar = require("devtools/client/inspector/extensions/reducers/sidebar");
 exports.fontOptions = require("devtools/client/inspector/fonts/reducers/font-options");
 exports.fonts = require("devtools/client/inspector/fonts/reducers/fonts");
 exports.grids = require("devtools/client/inspector/grids/reducers/grids");
 exports.highlighterSettings = require("devtools/client/inspector/grids/reducers/highlighter-settings");
--- a/devtools/client/locales/en-US/animationinspector.properties
+++ b/devtools/client/locales/en-US/animationinspector.properties
@@ -16,16 +16,21 @@
 # animated).
 panel.invalidElementSelected=No animations were found for the current element.
 
 # LOCALIZATION NOTE (panel.selectElement): This is the label shown in the panel
 # when an invalid node is currently selected in the inspector, to invite the
 # user to select a new node by clicking on the element-picker icon.
 panel.selectElement=Pick another element from the page.
 
+# LOCALIZATION NOTE (panel.invalidElementSelected):
+# This is the label shown in the panel when there are no displayable animations.
+# (e.g. In case of user selected a non-element node or a node that is not animated).
+panel.noAnimation=No animations were found for the current element.\nPick another element from the page.
+
 # LOCALIZATION NOTE (panel.allAnimations): This is the label shown at the bottom of
 # the panel, in a toolbar, to let the user know the toolbar applies to all
 # animations, not just the ones applying to the current element.
 panel.allAnimations=All animations
 
 # LOCALIZATION NOTE (player.animationDurationLabel):
 # This string is displayed in each animation player widget. It is the label
 # displayed before the animation duration.
--- a/devtools/client/themes/animation.css
+++ b/devtools/client/themes/animation.css
@@ -1,29 +1,47 @@
 /* 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/. */
 
 /* Animation-inspector specific theme variables */
 
 :root {
   --animation-even-background-color: rgba(0,0,0,0.05);
+  --command-pick-image: url(chrome://devtools/skin/images/command-pick.svg);
 }
 
 :root.theme-dark {
   --animation-even-background-color: rgba(255,255,255,0.05);
 }
 
+:root.theme-firebug {
+  --command-pick-image: url(chrome://devtools/skin/images/firebug/command-pick.svg);
+}
+
 /* Settings for animations element */
 .animation-list {
   list-style-type: none;
   margin-top: 0;
   padding: 0;
 }
 
 /* Settings for each animation element */
 .animation-item {
   height: 30px;
 }
 
 .animation-item:nth-child(2n+1) {
   background-color: var(--animation-even-background-color);
 }
+
+/* Settings for no animation message */
+.animation-error-message {
+  overflow: auto;
+}
+
+.animation-error-message > p {
+  white-space: pre;
+}
+
+.animation-element-picker::before {
+  background-image: var(--command-pick-image);
+}