Bug 1449893 - Implementation for managing variation instances. r=gl
authorRazvan Caliman <rcaliman@mozilla.com>
Fri, 20 Apr 2018 05:13:00 +0300
changeset 468503 07bfb2403ba51fd3d50ba513cbf2d0272476a296
parent 468502 df38004081b38e5b657d51878dc8b534e67ec08b
child 468504 1d87b41bac54668a11ca7103f152e373b130ae6e
push id9165
push userasasaki@mozilla.com
push dateThu, 26 Apr 2018 21:04:54 +0000
treeherdermozilla-beta@064c3804de2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgl
bugs1449893
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1449893 - Implementation for managing variation instances. r=gl MozReview-Commit-ID: DoJig5FAMQn
devtools/client/inspector/fonts/components/FontAxis.js
devtools/client/inspector/fonts/components/FontEditor.js
devtools/client/inspector/fonts/components/FontsApp.js
devtools/client/inspector/fonts/fonts.js
devtools/client/themes/fonts.css
--- a/devtools/client/inspector/fonts/components/FontAxis.js
+++ b/devtools/client/inspector/fonts/components/FontAxis.js
@@ -56,23 +56,28 @@ class FontAxis extends PureComponent {
         ...defaults,
         className: "font-axis-input",
         type: "number",
       }
     );
 
     return dom.label(
       {
-        className: "font-axis",
+        className: "font-control",
       },
       dom.span(
         {
-          className: "font-axis-label",
+          className: "font-control-label",
         },
         this.props.label
       ),
-      range,
-      this.props.showInput ? input : null
+      dom.div(
+        {
+          className: "font-control-input"
+        },
+        range,
+        this.props.showInput ? input : null
+      )
     );
   }
 }
 
 module.exports = FontAxis;
--- a/devtools/client/inspector/fonts/components/FontEditor.js
+++ b/devtools/client/inspector/fonts/components/FontEditor.js
@@ -5,23 +5,25 @@
 "use strict";
 
 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 FontAxis = createFactory(require("./FontAxis"));
 
+const { getStr } = require("../utils/l10n");
 const Types = require("../types");
 
 class FontEditor extends PureComponent {
   static get propTypes() {
     return {
       fontEditor: PropTypes.shape(Types.fontEditor).isRequired,
       onAxisUpdate: PropTypes.func.isRequired,
+      onInstanceChange: PropTypes.func.isRequired,
     };
   }
 
   /**
    * Naive implementation to get increment step for variable font axis that ensures
    * a wide spectrum of precision based on range of values between min and max.
    *
    * @param  {Number|String} min
@@ -68,33 +70,98 @@ class FontEditor extends PureComponent {
         label: axis.name,
         name: axis.tag,
         onChange: this.props.onAxisUpdate,
         showInput: true
       });
     });
   }
 
+  /**
+   * Get a dropdown which allows selecting between variation instances defined by a font.
+   *
+   * @param {Array} fontInstances
+   *        Named variation instances as provided with the font file.
+   * @param {Object} selectedInstance
+   *        Object with information about the currently selected variation instance.
+   *        Example:
+   *        {
+   *          name: "Custom",
+   *          values: []
+   *        }
+   * @return {DOMNode}
+   */
+  renderInstances(fontInstances = [], selectedInstance) {
+    // Append a "Custom" instance entry which represents the latest manual axes changes.
+    const customInstance = {
+      name: getStr("fontinspector.customInstanceName"),
+      values: this.props.fontEditor.customInstanceValues
+    };
+    fontInstances = [ ...fontInstances, customInstance ];
+
+    // Generate the <option> elements for the dropdown.
+    const instanceOptions = fontInstances.map(instance =>
+      dom.option(
+        {
+          value: instance.name,
+          selected: instance.name === selectedInstance.name ? "selected" : null,
+        },
+        instance.name
+      )
+    );
+
+    // Generate the dropdown.
+    const instanceSelect = dom.select(
+      {
+        className: "font-control-input",
+        onChange: (e) => {
+          const instance = fontInstances.find(inst => e.target.value === inst.name);
+          instance && this.props.onInstanceChange(instance.name, instance.values);
+        }
+      },
+      instanceOptions
+    );
+
+    return dom.label(
+      {
+        className: "font-control",
+      },
+      dom.span(
+        {
+          className: "font-control-label",
+        },
+        "Instances"
+      ),
+      instanceSelect
+    );
+  }
+
   // Placeholder for non-variable font UI.
   // Bug https://bugzilla.mozilla.org/show_bug.cgi?id=1450695
   renderPlaceholder() {
     return dom.div({}, "No fonts with variation axes apply to this element.");
   }
 
   render() {
-    const { fonts, axes } = this.props.fontEditor;
+    const { fonts, axes, instance } = this.props.fontEditor;
     // For MVP use ony first font to show axes if available.
     // Future implementations will allow switching between multiple fonts.
-    const fontAxes = (fonts[0] && fonts[0].variationAxes) ? fonts[0].variationAxes : null;
+    const font = fonts[0];
+    const fontAxes = (font && font.variationAxes) ? font.variationAxes : null;
+    const fontInstances = (font && font.variationInstances) ?
+      font.variationInstances
+      :
+      null;
 
     return dom.div(
       {
         className: "theme-sidebar inspector-tabpanel",
         id: "sidebar-panel-fontinspector"
       },
+      fontInstances && this.renderInstances(fontInstances, instance),
       fontAxes ?
         this.renderAxes(fontAxes, axes)
         :
         this.renderPlaceholder()
     );
   }
 }
 
--- a/devtools/client/inspector/fonts/components/FontsApp.js
+++ b/devtools/client/inspector/fonts/components/FontsApp.js
@@ -16,38 +16,41 @@ const Types = require("../types");
 
 class FontsApp extends PureComponent {
   static get propTypes() {
     return {
       fontData: PropTypes.shape(Types.fontData).isRequired,
       fontEditor: PropTypes.shape(Types.fontEditor).isRequired,
       fontOptions: PropTypes.shape(Types.fontOptions).isRequired,
       onAxisUpdate: PropTypes.func.isRequired,
+      onInstanceChange: PropTypes.func.isRequired,
       onPreviewFonts: PropTypes.func.isRequired,
     };
   }
 
   render() {
     const {
       fontData,
       fontEditor,
       fontOptions,
       onAxisUpdate,
+      onInstanceChange,
       onPreviewFonts,
     } = this.props;
 
     return dom.div(
       {
         className: "theme-sidebar inspector-tabpanel",
         id: "sidebar-panel-fontinspector"
       },
       fontEditor.isVisible ?
         FontEditor({
           fontEditor,
           onAxisUpdate,
+          onInstanceChange,
         })
         :
         FontOverview({
           fontData,
           fontOptions,
           onPreviewFonts,
         })
     );
--- a/devtools/client/inspector/fonts/fonts.js
+++ b/devtools/client/inspector/fonts/fonts.js
@@ -6,28 +6,37 @@
 
 "use strict";
 
 const { gDevTools } = require("devtools/client/framework/devtools");
 const { getColor } = require("devtools/client/shared/theme");
 const { createFactory, createElement } = require("devtools/client/shared/vendor/react");
 const { Provider } = require("devtools/client/shared/vendor/react-redux");
 const { throttle } = require("devtools/shared/throttle");
+const { debounce } = require("devtools/shared/debounce");
 
 const FontsApp = createFactory(require("./components/FontsApp"));
 
 const { LocalizationHelper } = require("devtools/shared/l10n");
 const INSPECTOR_L10N =
   new LocalizationHelper("devtools/client/locales/inspector.properties");
 
+const { getStr } = require("./utils/l10n");
 const { updateFonts } = require("./actions/fonts");
+const {
+  applyInstance,
+  resetFontEditor,
+  toggleFontEditor,
+  updateAxis,
+  updateCustomInstance,
+  updateFontEditor,
+} = require("./actions/font-editor");
 const { updatePreviewText } = require("./actions/font-options");
-const { resetFontEditor, toggleFontEditor, updateAxis, updateFontEditor } =
-  require("./actions/font-editor");
 
+const CUSTOM_INSTANCE_NAME = getStr("fontinspector.customInstanceName");
 const FONT_EDITOR_ID = "fonteditor";
 const FONT_PROPERTIES = [
   "font-optical-sizing",
   "font-size",
   "font-stretch",
   "font-style",
   "font-variation-settings",
   "font-weight",
@@ -37,18 +46,20 @@ class FontInspector {
   constructor(inspector, window) {
     this.document = window.document;
     this.inspector = inspector;
     this.pageStyle = this.inspector.pageStyle;
     this.ruleView = this.inspector.getPanel("ruleview").view;
     this.selectedRule = null;
     this.store = this.inspector.store;
 
+    this.snapshotChanges = debounce(this.snapshotChanges, 100, this);
     this.syncChanges = throttle(this.syncChanges, 100, this);
     this.onAxisUpdate = this.onAxisUpdate.bind(this);
+    this.onInstanceChange = this.onInstanceChange.bind(this);
     this.onNewNode = this.onNewNode.bind(this);
     this.onPreviewFonts = this.onPreviewFonts.bind(this);
     this.onRuleSelected = this.onRuleSelected.bind(this);
     this.onRuleUnselected = this.onRuleUnselected.bind(this);
     this.onRuleUpdated = this.onRuleUpdated.bind(this);
     this.onThemeChanged = this.onThemeChanged.bind(this);
     this.update = this.update.bind(this);
 
@@ -56,18 +67,19 @@ class FontInspector {
   }
 
   init() {
     if (!this.inspector) {
       return;
     }
 
     let fontsApp = FontsApp({
+      onAxisUpdate: this.onAxisUpdate,
+      onInstanceChange: this.onInstanceChange,
       onPreviewFonts: this.onPreviewFonts,
-      onAxisUpdate: this.onAxisUpdate,
     });
 
     let provider = createElement(Provider, {
       id: "fontinspector",
       key: "fontinspector",
       store: this.store,
       title: INSPECTOR_L10N.getStr("inspector.sidebar.fontInspectorTitle"),
     }, fontsApp);
@@ -208,16 +220,32 @@ class FontInspector {
    *
    * @param  {String} tag
    *         Tag name of the font axis.
    * @param  {String} value
    *         Value of the font axis.
    */
   onAxisUpdate(tag, value) {
     this.store.dispatch(updateAxis(tag, value));
+    this.store.dispatch(applyInstance(CUSTOM_INSTANCE_NAME, null));
+    this.snapshotChanges();
+    this.applyChanges();
+  }
+
+  /**
+   * Handler for selecting a font variation instance. Dispatches an action which updates
+   * the axes and their values as defined by that variation instance.
+   *
+   * @param {String} name
+   *        Name of variation instance. (ex: Light, Regular, Ultrabold, etc.)
+   * @param {Array} values
+   *        Array of objects with axes and values defined by the variation instance.
+   */
+  onInstanceChange(name, values) {
+    this.store.dispatch(applyInstance(name, values));
     this.applyChanges();
   }
 
   /**
    * Selection 'new-node' event handler.
    */
   onNewNode() {
     if (this.isPanelVisible()) {
@@ -329,16 +357,25 @@ class FontInspector {
 
     // Update the font editor state only if property values in rule differ from store.
     // This can happen when a user makes manual edits to the values in the rule view.
     if (JSON.stringify(properties) !== JSON.stringify(fontEditor.properties)) {
       this.store.dispatch(updateFontEditor(fonts, properties));
     }
   }
 
+  /**
+   * Capture the state of all variation axes. Allows the user to return to this state with
+   * the "Custom" instance after they've selected a font-defined named variation instance.
+   * This method is debounced. See constructor.
+   */
+  snapshotChanges() {
+    this.store.dispatch(updateCustomInstance());
+  }
+
   async update() {
     // Stop refreshing if the inspector or store is already destroyed.
     if (!this.inspector || !this.store) {
       return;
     }
 
     let node = this.inspector.selection.nodeFront;
 
--- a/devtools/client/themes/fonts.css
+++ b/devtools/client/themes/fonts.css
@@ -104,44 +104,65 @@
   color: var(--theme-body-color-inactive);
   border-radius: 3px;
   border-style: solid;
   border-width: 1px;
   text-align: center;
   vertical-align: middle;
 }
 
-.font-axis {
+.font-control {
   display: flex;
   flex-direction: row;
   flex-wrap: nowrap;
   justify-content: space-between;
   align-items: center;
   padding: 5px 20px;
 }
 
-.font-axis-input {
-  width: 60px;
+.font-control-input {
+  display: flex;
+  flex-wrap: nowrap;
+  align-items: center;
+  flex: 3;
+  max-width: 300px;
+  min-width: 100px;
 }
 
-.font-axis-label {
-  width: 70px;
+.font-control-label {
+  display: inline-block;
+  flex: 1;
+  min-width: 80px;
+}
+
+.font-axis-input {
+  margin-left: 10px;
+  width: 60px;
 }
 
 .font-axis-slider {
   flex: 1;
+  margin: 0;
+  min-width: 50px;
+}
+
+.font-instance-select:active{
+  outline: none;
 }
 
 .font-axis-slider::-moz-focus-outer {
   border: 0;
 }
 
 .font-axis-slider::-moz-range-thumb {
   background: var(--grey-50);
-  border: 0;
+  border-color: rgba(0, 0, 0, 0);
+}
+.font-axis-slider::-moz-range-track {
+  background: var(--grey-30);
 }
 
 .font-axis-slider:focus::-moz-range-thumb {
   background: var(--blue-55);
 }
 
 .font-origin {
   margin-top: .2em;