Bug 1416104 - Part 2: Make summary graph selectable. r?gl draft
authorDaisuke Akatsuka <dakatsuka@mozilla.com>
Fri, 19 Jan 2018 15:19:39 +0900
changeset 722537 c2832735edb042b841ea80e529ad7b82909122c4
parent 722536 7fe774d5d7db6db1d480acce287c2609e25cdfc5
child 722538 ee8f2e3e7302781d1ace4bee8f54f8419fcf5331
push id96167
push userbmo:dakatsuka@mozilla.com
push dateFri, 19 Jan 2018 07:35:17 +0000
reviewersgl
bugs1416104
milestone59.0a1
Bug 1416104 - Part 2: Make summary graph selectable. r?gl MozReview-Commit-ID: 7x3Z7uEzX8U
devtools/client/inspector/animation/actions/animations.js
devtools/client/inspector/animation/actions/index.js
devtools/client/inspector/animation/animation.js
devtools/client/inspector/animation/components/AnimationItem.js
devtools/client/inspector/animation/components/AnimationList.js
devtools/client/inspector/animation/components/AnimationListContainer.js
devtools/client/inspector/animation/components/App.js
devtools/client/inspector/animation/components/graph/SummaryGraph.js
devtools/client/inspector/animation/reducers/animations.js
devtools/client/themes/animation.css
--- a/devtools/client/inspector/animation/actions/animations.js
+++ b/devtools/client/inspector/animation/actions/animations.js
@@ -2,16 +2,17 @@
  * 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_ANIMATIONS,
   UPDATE_ELEMENT_PICKER_ENABLED,
+  UPDATE_SELECTED_ANIMATION,
   UPDATE_SIDEBAR_SIZE
 } = require("./index");
 
 module.exports = {
   /**
    * Update the list of animation in the animation inspector.
    */
   updateAnimations(animations) {
@@ -27,16 +28,26 @@ module.exports = {
   updateElementPickerEnabled(elementPickerEnabled) {
     return {
       type: UPDATE_ELEMENT_PICKER_ENABLED,
       elementPickerEnabled,
     };
   },
 
   /**
+   * Update selected animation.
+   */
+  updateSelectedAnimation(selectedAnimation) {
+    return {
+      type: UPDATE_SELECTED_ANIMATION,
+      selectedAnimation,
+    };
+  },
+
+  /**
    * Update the sidebar size.
    */
   updateSidebarSize(sidebarSize) {
     return {
       type: UPDATE_SIDEBAR_SIZE,
       sidebarSize,
     };
   }
--- a/devtools/client/inspector/animation/actions/index.js
+++ b/devtools/client/inspector/animation/actions/index.js
@@ -9,12 +9,15 @@ const { createEnum } = require("devtools
 createEnum([
 
   // Update the list of animation.
   "UPDATE_ANIMATIONS",
 
   // Update state of the picker enabled.
   "UPDATE_ELEMENT_PICKER_ENABLED",
 
+  // Update selected animation.
+  "UPDATE_SELECTED_ANIMATION",
+
   // Update sidebar size.
   "UPDATE_SIDEBAR_SIZE",
 
 ], module.exports);
--- a/devtools/client/inspector/animation/animation.js
+++ b/devtools/client/inspector/animation/animation.js
@@ -10,27 +10,29 @@ const { Provider } = require("devtools/c
 
 const EventEmitter = require("devtools/shared/event-emitter");
 
 const App = createFactory(require("./components/App"));
 
 const {
   updateAnimations,
   updateElementPickerEnabled,
+  updateSelectedAnimation,
   updateSidebarSize
 } = require("./actions/animations");
 const { isAllAnimationEqual } = require("./utils/utils");
 
 class AnimationInspector {
   constructor(inspector, win) {
     this.inspector = inspector;
     this.win = win;
 
     this.getAnimatedPropertyMap = this.getAnimatedPropertyMap.bind(this);
     this.getNodeFromActor = this.getNodeFromActor.bind(this);
+    this.selectAnimation = this.selectAnimation.bind(this);
     this.simulateAnimation = this.simulateAnimation.bind(this);
     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.onSidebarResized = this.onSidebarResized.bind(this);
     this.onSidebarSelect = this.onSidebarSelect.bind(this);
 
@@ -49,16 +51,17 @@ class AnimationInspector {
     const {
       onHideBoxModelHighlighter,
     } = this.inspector.getPanel("boxmodel").getComponentProps();
 
     const {
       emit: emitEventForTest,
       getAnimatedPropertyMap,
       getNodeFromActor,
+      selectAnimation,
       simulateAnimation,
       toggleElementPicker,
     } = this;
 
     const target = this.inspector.target;
     this.animationsFront = new AnimationsFront(target.client, target.form);
 
     const provider = createElement(Provider,
@@ -69,16 +72,17 @@ class AnimationInspector {
       },
       App(
         {
           emitEventForTest,
           getAnimatedPropertyMap,
           getNodeFromActor,
           onHideBoxModelHighlighter,
           onShowBoxModelHighlighterForNode,
+          selectAnimation,
           setSelectedNode,
           simulateAnimation,
           toggleElementPicker,
         }
       )
     );
     this.provider = provider;
 
@@ -222,16 +226,20 @@ class AnimationInspector {
     if (!this.animations || !isAllAnimationEqual(animations, this.animations)) {
       this.inspector.store.dispatch(updateAnimations(animations));
       this.animations = animations;
     }
 
     done();
   }
 
+  selectAnimation(animation) {
+    this.inspector.store.dispatch(updateSelectedAnimation(animation));
+  }
+
   onElementPickerStarted() {
     this.inspector.store.dispatch(updateElementPickerEnabled(true));
   }
 
   onElementPickerStopped() {
     this.inspector.store.dispatch(updateElementPickerEnabled(false));
   }
 
--- a/devtools/client/inspector/animation/components/AnimationItem.js
+++ b/devtools/client/inspector/animation/components/AnimationItem.js
@@ -1,69 +1,105 @@
 /* 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 { connect } = require("devtools/client/shared/vendor/react-redux");
 const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 
 const AnimationTarget = createFactory(require("./AnimationTarget"));
 const SummaryGraph = createFactory(require("./graph/SummaryGraph"));
 
 class AnimationItem extends PureComponent {
   static get propTypes() {
     return {
       animation: PropTypes.object.isRequired,
       emitEventForTest: PropTypes.func.isRequired,
       getAnimatedPropertyMap: PropTypes.func.isRequired,
       getNodeFromActor: PropTypes.func.isRequired,
       onHideBoxModelHighlighter: PropTypes.func.isRequired,
       onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
+      selectAnimation: PropTypes.func.isRequired,
+      selectedAnimation: PropTypes.object.isRequired,
       setSelectedNode: PropTypes.func.isRequired,
       simulateAnimation: PropTypes.func.isRequired,
       timeScale: PropTypes.object.isRequired,
     };
   }
 
+  constructor(props) {
+    super(props);
+
+    this.state = {
+      isSelected: false,
+    };
+  }
+
+  componentWillReceiveProps(nextProps) {
+    const { animation } = this.props;
+    this.setState({
+      isSelected: animation.actorID === nextProps.selectedAnimation.actorID
+    });
+  }
+
+  shouldComponentUpdate(nextProps, nextState) {
+    return this.state.isSelected !== nextState.isSelected ||
+           this.props.animation !== nextProps.animation ||
+           this.props.timeScale !== nextProps.timeScale;
+  }
+
   render() {
     const {
       animation,
       emitEventForTest,
       getAnimatedPropertyMap,
       getNodeFromActor,
       onHideBoxModelHighlighter,
       onShowBoxModelHighlighterForNode,
+      selectAnimation,
       setSelectedNode,
       simulateAnimation,
       timeScale,
     } = this.props;
+    const {
+      isSelected,
+    } = this.state;
 
     return dom.li(
       {
-        className: `animation-item ${ animation.state.type }`
+        className: `animation-item ${ animation.state.type } ` +
+                   (isSelected ? "selected" : ""),
       },
       AnimationTarget(
         {
           animation,
           emitEventForTest,
           getNodeFromActor,
           onHideBoxModelHighlighter,
           onShowBoxModelHighlighterForNode,
           setSelectedNode,
         }
       ),
       SummaryGraph(
         {
           animation,
           emitEventForTest,
           getAnimatedPropertyMap,
+          selectAnimation,
           simulateAnimation,
           timeScale,
         }
       )
     );
   }
 }
 
-module.exports = AnimationItem;
+const mapStateToProps = state => {
+  return {
+    selectedAnimation: state.animations.selectedAnimation,
+  };
+};
+
+module.exports = connect(mapStateToProps)(AnimationItem);
--- a/devtools/client/inspector/animation/components/AnimationList.js
+++ b/devtools/client/inspector/animation/components/AnimationList.js
@@ -14,30 +14,32 @@ class AnimationList extends PureComponen
   static get propTypes() {
     return {
       animations: PropTypes.arrayOf(PropTypes.object).isRequired,
       emitEventForTest: PropTypes.func.isRequired,
       getAnimatedPropertyMap: PropTypes.func.isRequired,
       getNodeFromActor: PropTypes.func.isRequired,
       onHideBoxModelHighlighter: PropTypes.func.isRequired,
       onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
+      selectAnimation: PropTypes.func.isRequired,
       setSelectedNode: PropTypes.func.isRequired,
       simulateAnimation: PropTypes.func.isRequired,
       timeScale: PropTypes.object.isRequired,
     };
   }
 
   render() {
     const {
       animations,
       emitEventForTest,
       getAnimatedPropertyMap,
       getNodeFromActor,
       onHideBoxModelHighlighter,
       onShowBoxModelHighlighterForNode,
+      selectAnimation,
       setSelectedNode,
       simulateAnimation,
       timeScale,
     } = this.props;
 
     return dom.ul(
       {
         className: "animation-list"
@@ -46,16 +48,17 @@ class AnimationList extends PureComponen
         AnimationItem(
           {
             animation,
             emitEventForTest,
             getAnimatedPropertyMap,
             getNodeFromActor,
             onHideBoxModelHighlighter,
             onShowBoxModelHighlighterForNode,
+            selectAnimation,
             setSelectedNode,
             simulateAnimation,
             timeScale,
           }
         )
       )
     );
   }
--- a/devtools/client/inspector/animation/components/AnimationListContainer.js
+++ b/devtools/client/inspector/animation/components/AnimationListContainer.js
@@ -18,29 +18,31 @@ class AnimationListContainer extends Pur
   static get propTypes() {
     return {
       animations: PropTypes.arrayOf(PropTypes.object).isRequired,
       emitEventForTest: PropTypes.func.isRequired,
       getAnimatedPropertyMap: PropTypes.func.isRequired,
       getNodeFromActor: PropTypes.func.isRequired,
       onHideBoxModelHighlighter: PropTypes.func.isRequired,
       onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
+      selectAnimation: PropTypes.func.isRequired,
       setSelectedNode: PropTypes.func.isRequired,
       simulateAnimation: PropTypes.func.isRequired,
     };
   }
 
   render() {
     const {
       animations,
       emitEventForTest,
       getAnimatedPropertyMap,
       getNodeFromActor,
       onHideBoxModelHighlighter,
       onShowBoxModelHighlighterForNode,
+      selectAnimation,
       setSelectedNode,
       simulateAnimation,
     } = this.props;
     const timeScale = new TimeScale(animations);
 
     return dom.div(
       {
         className: "animation-list-container"
@@ -53,16 +55,17 @@ class AnimationListContainer extends Pur
       AnimationList(
         {
           animations,
           emitEventForTest,
           getAnimatedPropertyMap,
           getNodeFromActor,
           onHideBoxModelHighlighter,
           onShowBoxModelHighlighterForNode,
+          selectAnimation,
           setSelectedNode,
           simulateAnimation,
           timeScale,
         }
       )
     );
   }
 }
--- a/devtools/client/inspector/animation/components/App.js
+++ b/devtools/client/inspector/animation/components/App.js
@@ -18,16 +18,17 @@ class App extends PureComponent {
   static get propTypes() {
     return {
       animations: PropTypes.arrayOf(PropTypes.object).isRequired,
       emitEventForTest: PropTypes.func.isRequired,
       getAnimatedPropertyMap: PropTypes.func.isRequired,
       getNodeFromActor: PropTypes.func.isRequired,
       onHideBoxModelHighlighter: PropTypes.func.isRequired,
       onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
+      selectAnimation: PropTypes.func.isRequired,
       setSelectedNode: PropTypes.func.isRequired,
       simulateAnimation: PropTypes.func.isRequired,
       toggleElementPicker: PropTypes.func.isRequired,
     };
   }
 
   shouldComponentUpdate(nextProps, nextState) {
     return this.props.animations.length !== 0 || nextProps.animations.length !== 0;
@@ -36,16 +37,17 @@ class App extends PureComponent {
   render() {
     const {
       animations,
       emitEventForTest,
       getAnimatedPropertyMap,
       getNodeFromActor,
       onHideBoxModelHighlighter,
       onShowBoxModelHighlighterForNode,
+      selectAnimation,
       setSelectedNode,
       simulateAnimation,
       toggleElementPicker,
     } = this.props;
 
     return dom.div(
       {
         id: "animation-container"
@@ -60,16 +62,17 @@ class App extends PureComponent {
         startPanel: AnimationListContainer(
           {
             animations,
             emitEventForTest,
             getAnimatedPropertyMap,
             getNodeFromActor,
             onHideBoxModelHighlighter,
             onShowBoxModelHighlighterForNode,
+            selectAnimation,
             setSelectedNode,
             simulateAnimation,
           }
         ),
         vert: false,
       })
       :
       NoAnimationPanel(
--- a/devtools/client/inspector/animation/components/graph/SummaryGraph.js
+++ b/devtools/client/inspector/animation/components/graph/SummaryGraph.js
@@ -16,21 +16,32 @@ const SummaryGraphPath = createFactory(r
 const { getFormatStr, getStr, numberWithDecimals } = require("../../utils/l10n");
 
 class SummaryGraph extends PureComponent {
   static get propTypes() {
     return {
       animation: PropTypes.object.isRequired,
       emitEventForTest: PropTypes.func.isRequired,
       getAnimatedPropertyMap: PropTypes.func.isRequired,
+      selectAnimation: PropTypes.func.isRequired,
       simulateAnimation: PropTypes.func.isRequired,
       timeScale: PropTypes.object.isRequired,
     };
   }
 
+  constructor(props) {
+    super(props);
+
+    this.onClick = this.onClick.bind(this);
+  }
+
+  onClick() {
+    this.props.selectAnimation(this.props.animation);
+  }
+
   getTitleText(state) {
     const getTime =
       time => getFormatStr("player.timeLabel", numberWithDecimals(time / 1000, 2));
 
     let text = "";
 
     // Adding the name.
     text += getFormattedTitle(state);
@@ -130,16 +141,17 @@ class SummaryGraph extends PureComponent
       simulateAnimation,
       timeScale,
     } = this.props;
 
     return dom.div(
       {
         className: "animation-summary-graph" +
                    (animation.state.isRunningOnCompositor ? " compositor" : ""),
+        onClick: this.onClick,
         title: this.getTitleText(animation.state),
       },
       SummaryGraphPath(
         {
           animation,
           emitEventForTest,
           getAnimatedPropertyMap,
           simulateAnimation,
--- a/devtools/client/inspector/animation/reducers/animations.js
+++ b/devtools/client/inspector/animation/reducers/animations.js
@@ -2,22 +2,24 @@
  * 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_ANIMATIONS,
   UPDATE_ELEMENT_PICKER_ENABLED,
+  UPDATE_SELECTED_ANIMATION,
   UPDATE_SIDEBAR_SIZE,
 } = require("../actions/index");
 
 const INITIAL_STATE = {
   animations: [],
   elementPickerEnabled: false,
+  selectedAnimation: null,
   sidebarSize: {
     height: 0,
     width: 0,
   },
 };
 
 const reducers = {
   [UPDATE_ANIMATIONS](state, { animations }) {
@@ -27,16 +29,22 @@ const reducers = {
   },
 
   [UPDATE_ELEMENT_PICKER_ENABLED](state, { elementPickerEnabled }) {
     return Object.assign({}, state, {
       elementPickerEnabled
     });
   },
 
+  [UPDATE_SELECTED_ANIMATION](state, { selectedAnimation }) {
+    return Object.assign({}, state, {
+      selectedAnimation
+    });
+  },
+
   [UPDATE_SIDEBAR_SIZE](state, { sidebarSize }) {
     return Object.assign({}, state, {
       sidebarSize
     });
   },
 };
 
 module.exports = function (state = INITIAL_STATE, action) {
--- a/devtools/client/themes/animation.css
+++ b/devtools/client/themes/animation.css
@@ -82,16 +82,20 @@
   --effect-timing-graph-color: var(--theme-highlight-bluegrey);
 }
 
 .animation-item.scriptanimation {
   --computed-timing-graph-color: var(--theme-graphs-green);
   --effect-timing-graph-color: var(--theme-highlight-green);
 }
 
+.animation-item.selected {
+  background-color: var(--theme-selection-background-hover);
+}
+
 /* Animation Target */
 .animation-target {
   align-items: center;
   display: flex;
   height: 100%;
   padding-left: 4px;
   width: var(--sidebar-width);
 }