Bug 1210796 - Part 14: Add close button. r=pbro draft
authorDaisuke Akatsuka <dakatsuka@mozilla.com>
Tue, 18 Apr 2017 12:15:56 +0900
changeset 564127 24990eab85d69b26128d05a2523b5241b82d69ba
parent 564126 254d333f555fc715a166670cb2d89d7031017b32
child 564128 38e2e3d2ac8330246354f6e33cd4d3e6cffd7100
push id54524
push userbmo:dakatsuka@mozilla.com
push dateTue, 18 Apr 2017 09:24:06 +0000
reviewerspbro
bugs1210796
milestone55.0a1
Bug 1210796 - Part 14: Add close button. r=pbro MozReview-Commit-ID: 59NdVAtgeLw
devtools/client/animationinspector/components/animation-timeline.js
devtools/client/animationinspector/test/browser_animation_detail_displayed.js
devtools/client/locales/en-US/animationinspector.properties
devtools/client/themes/animationinspector.css
--- a/devtools/client/animationinspector/components/animation-timeline.js
+++ b/devtools/client/animationinspector/components/animation-timeline.js
@@ -54,16 +54,17 @@ function AnimationsTimeline(inspector, s
   this.onAnimationStateChanged = this.onAnimationStateChanged.bind(this);
   this.onScrubberMouseDown = this.onScrubberMouseDown.bind(this);
   this.onScrubberMouseUp = this.onScrubberMouseUp.bind(this);
   this.onScrubberMouseOut = this.onScrubberMouseOut.bind(this);
   this.onScrubberMouseMove = this.onScrubberMouseMove.bind(this);
   this.onAnimationSelected = this.onAnimationSelected.bind(this);
   this.onWindowResize = this.onWindowResize.bind(this);
   this.onTimelineDataChanged = this.onTimelineDataChanged.bind(this);
+  this.onDetailCloseButtonClick = this.onDetailCloseButtonClick.bind(this);
 
   EventEmitter.decorate(this);
 }
 
 exports.AnimationsTimeline = AnimationsTimeline;
 
 AnimationsTimeline.prototype = {
   init: function (containerEl) {
@@ -87,30 +88,31 @@ AnimationsTimeline.prototype = {
     const React = browserRequire("devtools/client/shared/vendor/react");
     const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
 
     const SplitBox = React.createFactory(
       browserRequire("devtools/client/shared/components/splitter/split-box"));
 
     const splitter = SplitBox({
       className: "animation-root",
-      initialSize: "0 0",
-      maxSize: "calc(100% - (var(--timeline-animation-height) * 2))",
       splitterSize: 1,
+      initialHeight: "50%",
       endPanelControl: true,
       startPanel: React.DOM.div({
         className: "animation-timeline"
       }),
       endPanel: React.DOM.div({
         className: "animation-detail"
       }),
       vert: false
     });
 
     ReactDOM.render(splitter, this.rootWrapperEl);
+
+    this.animationRootEl = this.rootWrapperEl.querySelector(".animation-root");
   },
 
   setupAnimationTimeline: function () {
     const animationTimelineEl = this.rootWrapperEl.querySelector(".animation-timeline");
 
     let scrubberContainer = createNode({
       parent: animationTimelineEl,
       attributes: {"class": "scrubber-wrapper"}
@@ -167,24 +169,20 @@ AnimationsTimeline.prototype = {
       nodeType: "ul",
       attributes: {
         "class": "animations"
       }
     });
   },
 
   setupAnimationDetail: function () {
-    this.animationDetailEl = this.rootWrapperEl.querySelector(".animation-detail");
-
-    this.animationDetailEl.dataset.defaultDisplayStyle =
-      this.win.getComputedStyle(this.animationDetailEl).display;
-    this.animationDetailEl.style.display = "none";
+    const animationDetailEl = this.rootWrapperEl.querySelector(".animation-detail");
 
     const animationDetailHeaderEl = createNode({
-      parent: this.animationDetailEl,
+      parent: animationDetailEl,
       attributes: {
         "class": "animation-detail-header"
       }
     });
 
     const headerTitleEl = createNode({
       parent: animationDetailHeaderEl,
       attributes: {
@@ -196,18 +194,29 @@ AnimationsTimeline.prototype = {
       parent: headerTitleEl,
       textContent: L10N.getStr("detail.headerTitle")
     });
 
     this.animationAnimationNameEl = createNode({
       parent: headerTitleEl
     });
 
+    this.animationDetailCloseButton = createNode({
+      parent: headerTitleEl,
+      nodeType: "button",
+      attributes: {
+        "class": "devtools-button",
+        title: L10N.getStr("detail.header.closeLabel"),
+      }
+    });
+    this.animationDetailCloseButton.addEventListener("click",
+                                                     this.onDetailCloseButtonClick);
+
     const animationDetailBodyEl = createNode({
-      parent: this.animationDetailEl,
+      parent: animationDetailEl,
       attributes: {
         "class": "animation-detail-body"
       }
     });
 
     this.animatedPropertiesEl = createNode({
       parent: animationDetailBodyEl,
       attributes: {
@@ -225,31 +234,35 @@ AnimationsTimeline.prototype = {
     this.details.destroy();
 
     this.win.removeEventListener("resize",
       this.onWindowResize);
     this.timeHeaderEl.removeEventListener("mousedown",
       this.onScrubberMouseDown);
     this.scrubberHandleEl.removeEventListener("mousedown",
       this.onScrubberMouseDown);
+    this.animationDetailCloseButton.removeEventListener("click",
+      this.onDetailCloseButtonClick);
 
     this.rootWrapperEl.remove();
     this.animations = [];
     this.rootWrapperEl = null;
     this.timeHeaderEl = null;
     this.animationsEl = null;
     this.animatedPropertiesEl = null;
     this.scrubberEl = null;
     this.scrubberHandleEl = null;
     this.win = null;
     this.inspector = null;
     this.serverTraits = null;
     this.animationDetailEl = null;
     this.animationAnimationNameEl = null;
     this.animatedPropertiesEl = null;
+    this.animationDetailCloseButton = null;
+    this.animationRootEl = null;
   },
 
   /**
    * Destroy sub-components that have been created and stored on this instance.
    * @param {String} name An array of components will be expected in this[name]
    * @param {Array} handlers An option list of event handlers information that
    * should be used to remove these handlers.
    */
@@ -318,18 +331,17 @@ AnimationsTimeline.prototype = {
     if (!this.animatedPropertiesEl.classList.contains(animation.state.type)) {
       this.animatedPropertiesEl.className =
         `animated-properties ${ animation.state.type }`;
     }
 
     // Select and render.
     const selectedAnimationEl = animationEls[index];
     selectedAnimationEl.classList.add("selected");
-    this.animationDetailEl.style.display =
-      this.animationDetailEl.dataset.defaultDisplayStyle;
+    this.animationRootEl.classList.add("animation-detail-visible");
     yield this.details.render(animation);
     this.onTimelineDataChanged(null, { time: this.currentTime || 0 });
     this.animationAnimationNameEl.textContent = getFormattedAnimationTitle(animation);
     this.emit("animation-selected", animation);
   }),
 
   /**
    * When move the scrubber to the corresponding position
@@ -599,10 +611,15 @@ AnimationsTimeline.prototype = {
     }
   },
 
   onTimelineDataChanged: function (e, { time }) {
     this.currentTime = time;
     const indicateTime =
       TimeScale.minStartTime === Infinity ? 0 : this.currentTime + TimeScale.minStartTime;
     this.details.indicateProgress(indicateTime);
+  },
+
+  onDetailCloseButtonClick: function (e) {
+    this.animationRootEl.classList.remove("animation-detail-visible");
+    this.emit("animation-detail-closed");
   }
 };
--- a/devtools/client/animationinspector/test/browser_animation_detail_displayed.js
+++ b/devtools/client/animationinspector/test/browser_animation_detail_displayed.js
@@ -5,37 +5,56 @@
 "use strict";
 
 // Tests the behavior of animation-detail container.
 // We test following cases.
 // 1. Existance of animation-detail element.
 // 2. Hidden at first if multiple animations were displayed.
 // 3. Display after click on an animation.
 // 4. Display from first time if displayed animation is only one.
+// 5. Close the animation-detail element by clicking on close button.
 
 requestLongerTimeout(5);
 
 add_task(function* () {
   yield addTab(URL_ROOT + "doc_multiple_property_types.html");
   const { panel, inspector } = yield openAnimationInspector();
   const timelineComponent = panel.animationsTimelineComponent;
   const animationDetailEl =
     timelineComponent.rootWrapperEl.querySelector(".animation-detail");
+  const splitboxControlledEl =
+    timelineComponent.rootWrapperEl.querySelector(".controlled");
 
   // 1. Existance of animation-detail element.
   ok(animationDetailEl, "The animation-detail element should exist");
 
   // 2. Hidden at first if multiple animations were displayed.
   const win = animationDetailEl.ownerDocument.defaultView;
-  is(win.getComputedStyle(animationDetailEl).display, "none",
+  is(win.getComputedStyle(splitboxControlledEl).display, "none",
      "The animation-detail element should be hidden at first "
      + "if multiple animations were displayed");
 
   // 3. Display after click on an animation.
   yield clickOnAnimation(panel, 0);
-  isnot(win.getComputedStyle(animationDetailEl).display, "none",
+  isnot(win.getComputedStyle(splitboxControlledEl).display, "none",
         "The animation-detail element should be displayed after clicked on an animation");
 
   // 4. Display from first time if displayed animation is only one.
   yield selectNodeAndWaitForAnimations("#target1", inspector);
   ok(animationDetailEl.querySelector(".property"),
      "The property in animation-detail element should be displayed");
+
+  // 5. Close the animation-detail element by clicking on close button.
+  const previousHeight = animationDetailEl.offsetHeight;
+  const button = animationDetailEl.querySelector(".animation-detail-header button");
+  const onclosed = timelineComponent.once("animation-detail-closed");
+  EventUtils.sendMouseEvent({type: "click"}, button, win);
+  yield onclosed;
+  is(win.getComputedStyle(splitboxControlledEl).display, "none",
+     "animation-detail element should not display");
+
+  // Select another animation.
+  yield selectNodeAndWaitForAnimations("#target2", inspector);
+  isnot(win.getComputedStyle(splitboxControlledEl).display, "none",
+        "animation-detail element should display");
+  is(animationDetailEl.offsetHeight, previousHeight,
+     "The height of animation-detail should keep the height");
 });
--- a/devtools/client/locales/en-US/animationinspector.properties
+++ b/devtools/client/locales/en-US/animationinspector.properties
@@ -177,8 +177,12 @@ timeline.unknown.nameLabel=%S
 # This string is displayed on header label in .animated-properties-header.
 # %S represents the value in percentage with two decimal points, localized.
 # there are two "%" after %S to escape and display "%"
 detail.propertiesHeader.percentage=%S%%
 
 # LOCALIZATION NOTE (detail.headerTitle):
 # This string is displayed on header label in .animation-detail-header.
 detail.headerTitle=Animated properties for
+
+# LOCALIZATION NOTE (detail.header.closeLabel):
+# This string is displayed in a tooltip of close button for animated properties
+detail.header.closeLabel=Close animated properties panel
--- a/devtools/client/themes/animationinspector.css
+++ b/devtools/client/themes/animationinspector.css
@@ -5,16 +5,17 @@
 /* Animation-inspector specific theme variables */
 
 .theme-dark {
   --even-animation-timeline-background-color: rgba(255,255,255,0.03);
   --command-pick-image: url(chrome://devtools/skin/images/command-pick.svg);
   --pause-image: url(chrome://devtools/skin/images/pause.svg);
   --rewind-image: url(chrome://devtools/skin/images/rewind.svg);
   --play-image: url(chrome://devtools/skin/images/play.svg);
+  --close-button-image: url(chrome://devtools/skin/images/close.svg);
   /* The color for animation type 'opacity' */
   --opacity-border-color: var(--theme-highlight-pink);
   --opacity-background-color: #df80ff80;
   /* The color for animation type 'transform' */
   --transform-border-color: var(--theme-graphs-yellow);
   --transform-background-color: #d99b2880;
   /* The color for other animation type */
   --other-border-color: var(--theme-graphs-bluegrey);
@@ -24,24 +25,26 @@
 }
 
 .theme-light {
   --even-animation-timeline-background-color: rgba(128,128,128,0.03);
   --command-pick-image: url(chrome://devtools/skin/images/command-pick.svg);
   --pause-image: url(chrome://devtools/skin/images/pause.svg);
   --rewind-image: url(chrome://devtools/skin/images/rewind.svg);
   --play-image: url(chrome://devtools/skin/images/play.svg);
+  --close-button-image: url(chrome://devtools/skin/images/close.svg);
 }
 
 .theme-firebug {
   --even-animation-timeline-background-color: rgba(128,128,128,0.03);
   --command-pick-image: url(chrome://devtools/skin/images/firebug/command-pick.svg);
   --pause-image: url(chrome://devtools/skin/images/firebug/pause.svg);
   --rewind-image: url(chrome://devtools/skin/images/firebug/rewind.svg);
   --play-image: url(chrome://devtools/skin/images/firebug/play.svg);
+  --close-button-image: url(chrome://devtools/skin/images/firebug/close.svg);
 }
 
 .theme-light, .theme-firebug {
   /* The color for animation type 'opacity' */
   --opacity-border-color: var(--theme-highlight-pink);
   --opacity-background-color: #b82ee580;
   /* The color for animation type 'transform' */
   --transform-border-color: var(--theme-graphs-orange);
@@ -683,41 +686,55 @@ body {
 .animation-detail {
   position: relative;
   width: 100%;
   background-color: var(--theme-body-background);
   z-index: 5;
 }
 
 .animation-detail .animation-detail-header {
+  position: relative;
   height: var(--toolbar-height);
   width: 100%;
 }
 
 .animation-detail .animation-detail-header > div {
   position: fixed;
   display: flex;
   flex-wrap: nowrap;
   width: 100%;
   height: var(--toolbar-height);
   line-height: var(--toolbar-height);
   background-color: var(--theme-body-background);
+  padding: 0;
   z-index: 5;
 }
 
 .animation-detail .animation-detail-header > div > div {
   white-space: nowrap;
 }
 
 .animation-detail .animation-detail-header > div > div:first-child {
   margin-left: 15px;
 }
 
 .animation-detail .animation-detail-header > div > div:nth-child(2) {
+  flex: 1;
   margin-left: .5em;
+  min-width: 0;
+}
+
+.animation-detail .animation-detail-header .devtools-button {
+  /* We need to tweak the padding
+     since the devtools-button is optimized for toolbox-tab height */
+  padding-top: 0;
+}
+
+.animation-detail .animation-detail-header .devtools-button::before {
+  background-image: var(--close-button-image);
 }
 
 .animation-detail .animation-detail-body {
   position: relative;
   background-color: var(--theme-body-background);
 }
 
 .animation-detail .animation-detail-body .animated-properties {
@@ -780,8 +797,12 @@ body {
   position: absolute;
   top: 0;
   right: -6px;
   width: 1px;
   border-top: 5px solid var(--progress-indicator-color);
   border-left: 5px solid transparent;
   border-right: 5px solid transparent;
 }
+
+.animation-root:not(.animation-detail-visible) .controlled {
+  display: none;
+}