Bug 1443846 - Add skeleton for font editor panel. r=gl
- Implement basic React component & Redux store and actions for font editor.
- Move font overview rendering from FontsApp into its own component: FontOverview. FontsApp remains just a wrapper for FontEditor and FontOverview.
- Listen to rule selection events to toggle the display of the font editor and font overview panels.
MozReview-Commit-ID: 496LHPqpnKL
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/fonts/actions/font-editor.js
@@ -0,0 +1,21 @@
+/* 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_EDITOR_VISIBILITY,
+} = require("./index");
+
+module.exports = {
+
+ toggleFontEditor(isVisible, selector) {
+ return {
+ type: UPDATE_EDITOR_VISIBILITY,
+ isVisible,
+ selector,
+ };
+ },
+
+};
--- a/devtools/client/inspector/fonts/actions/index.js
+++ b/devtools/client/inspector/fonts/actions/index.js
@@ -3,15 +3,18 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { createEnum } = require("devtools/client/shared/enum");
createEnum([
+ // Toggle the visibiltiy of the font editor
+ "UPDATE_EDITOR_VISIBILITY",
+
// Update the list of fonts.
"UPDATE_FONTS",
// Update the preview text.
"UPDATE_PREVIEW_TEXT",
], module.exports);
--- a/devtools/client/inspector/fonts/actions/moz.build
+++ b/devtools/client/inspector/fonts/actions/moz.build
@@ -1,11 +1,12 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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(
+ 'font-editor.js',
'font-options.js',
'fonts.js',
'index.js',
)
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/fonts/components/FontEditor.js
@@ -0,0 +1,31 @@
+/* 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 { PureComponent } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+const Types = require("../types");
+
+class FontEditor extends PureComponent {
+ static get propTypes() {
+ return {
+ fontEditor: PropTypes.shape(Types.fontEditor).isRequired,
+ };
+ }
+
+ render() {
+ const { selector } = this.props.fontEditor;
+
+ return dom.div(
+ {
+ className: "theme-sidebar inspector-tabpanel",
+ id: "sidebar-panel-fonteditor"
+ }, `Placeholder for Font Editor panel for selector: ${selector}`
+ );
+ }
+}
+
+module.exports = FontEditor;
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/fonts/components/FontOverview.js
@@ -0,0 +1,88 @@
+/* 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 { 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 Accordion = createFactory(require("devtools/client/inspector/layout/components/Accordion"));
+const FontList = createFactory(require("./FontList"));
+
+const { getStr } = require("../utils/l10n");
+const Types = require("../types");
+
+class FontOverview extends PureComponent {
+ static get propTypes() {
+ return {
+ fontData: PropTypes.shape(Types.fontData).isRequired,
+ fontOptions: PropTypes.shape(Types.fontOptions).isRequired,
+ onPreviewFonts: PropTypes.func.isRequired,
+ };
+ }
+
+ renderElementFonts() {
+ let {
+ fontData,
+ fontOptions,
+ onPreviewFonts,
+ } = this.props;
+ let { fonts } = fontData;
+
+ return fonts.length ?
+ FontList({
+ fonts,
+ fontOptions,
+ onPreviewFonts
+ })
+ :
+ dom.div(
+ {
+ className: "devtools-sidepanel-no-result"
+ },
+ getStr("fontinspector.noFontsOnSelectedElement")
+ );
+ }
+
+ renderOtherFonts() {
+ let {
+ fontData,
+ onPreviewFonts,
+ fontOptions,
+ } = this.props;
+ let { otherFonts } = fontData;
+
+ if (!otherFonts.length) {
+ return null;
+ }
+
+ return Accordion({
+ items: [
+ {
+ header: getStr("fontinspector.otherFontsInPageHeader"),
+ component: FontList,
+ componentProps: {
+ fontOptions,
+ fonts: otherFonts,
+ onPreviewFonts
+ },
+ opened: false
+ }
+ ]
+ });
+ }
+
+ render() {
+ return dom.div(
+ {
+ id: "font-container",
+ },
+ this.renderElementFonts(),
+ this.renderOtherFonts()
+ );
+ }
+}
+
+module.exports = FontOverview;
--- a/devtools/client/inspector/fonts/components/FontsApp.js
+++ b/devtools/client/inspector/fonts/components/FontsApp.js
@@ -4,92 +4,51 @@
"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 { connect } = require("devtools/client/shared/vendor/react-redux");
-const Accordion = createFactory(require("devtools/client/inspector/layout/components/Accordion"));
-const FontList = createFactory(require("./FontList"));
+const FontEditor = createFactory(require("./FontEditor"));
+const FontOverview = createFactory(require("./FontOverview"));
-const { getStr } = require("../utils/l10n");
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,
onPreviewFonts: PropTypes.func.isRequired,
};
}
- renderElementFonts() {
- let {
- fontData,
- fontOptions,
- onPreviewFonts,
- } = this.props;
- let { fonts } = fontData;
-
- return fonts.length ?
- FontList({
- fonts,
- fontOptions,
- onPreviewFonts
- })
- :
- dom.div(
- {
- className: "devtools-sidepanel-no-result"
- },
- getStr("fontinspector.noFontsOnSelectedElement")
- );
- }
-
- renderOtherFonts() {
- let {
+ render() {
+ const {
fontData,
- onPreviewFonts,
+ fontEditor,
fontOptions,
+ onPreviewFonts
} = this.props;
- let { otherFonts } = fontData;
-
- if (!otherFonts.length) {
- return null;
- }
- return Accordion({
- items: [
- {
- header: getStr("fontinspector.otherFontsInPageHeader"),
- component: FontList,
- componentProps: {
- fontOptions,
- fonts: otherFonts,
- onPreviewFonts
- },
- opened: false
- }
- ]
- });
- }
-
- render() {
return dom.div(
{
className: "theme-sidebar inspector-tabpanel",
id: "sidebar-panel-fontinspector"
},
- dom.div(
- {
- id: "font-container"
- },
- this.renderElementFonts(),
- this.renderOtherFonts()
- )
+ this.props.fontEditor.isVisible ?
+ FontEditor({
+ fontEditor,
+ })
+ :
+ FontOverview({
+ fontData,
+ fontOptions,
+ onPreviewFonts,
+ })
);
}
}
module.exports = connect(state => state)(FontsApp);
--- a/devtools/client/inspector/fonts/components/moz.build
+++ b/devtools/client/inspector/fonts/components/moz.build
@@ -1,12 +1,14 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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(
'Font.js',
+ 'FontEditor.js',
'FontList.js',
+ 'FontOverview.js',
'FontPreview.js',
'FontsApp.js',
)
--- a/devtools/client/inspector/fonts/fonts.js
+++ b/devtools/client/inspector/fonts/fonts.js
@@ -14,28 +14,34 @@ const { Provider } = require("devtools/c
const FontsApp = createFactory(require("./components/FontsApp"));
const { LocalizationHelper } = require("devtools/shared/l10n");
const INSPECTOR_L10N =
new LocalizationHelper("devtools/client/locales/inspector.properties");
const { updateFonts } = require("./actions/fonts");
const { updatePreviewText } = require("./actions/font-options");
+const { toggleFontEditor } = require("./actions/font-editor");
+
+const FONT_EDITOR_ID = "fonteditor";
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.update = this.update.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.onThemeChanged = this.onThemeChanged.bind(this);
this.init();
}
init() {
if (!this.inspector) {
return;
@@ -52,16 +58,18 @@ class FontInspector {
title: INSPECTOR_L10N.getStr("inspector.sidebar.fontInspectorTitle"),
}, fontsApp);
// Expose the provider to let inspector.js use it in setupSidebar.
this.provider = provider;
this.inspector.selection.on("new-node-front", this.onNewNode);
this.inspector.sidebar.on("fontinspector-selected", this.onNewNode);
+ this.ruleView.on("ruleview-rule-selected", this.onRuleSelected);
+ this.ruleView.on("ruleview-rule-unselected", this.onRuleUnselected);
// Listen for theme changes as the color of the previews depend on the theme
gDevTools.on("theme-switched", this.onThemeChanged);
this.store.dispatch(updatePreviewText(""));
this.update(false, "");
}
@@ -84,21 +92,25 @@ class FontInspector {
/**
* Destruction function called when the inspector is destroyed. Removes event listeners
* and cleans up references.
*/
destroy() {
this.inspector.selection.off("new-node-front", this.onNewNode);
this.inspector.sidebar.off("fontinspector-selected", this.onNewNode);
+ this.ruleView.off("ruleview-rule-selected", this.onRuleSelected);
+ this.ruleView.off("ruleview-rule-unselected", this.onRuleUnselected);
gDevTools.off("theme-switched", this.onThemeChanged);
this.document = null;
this.inspector = null;
this.pageStyle = null;
+ this.ruleView = null;
+ this.selectedRule = null;
this.store = null;
}
async getFontsForNode(node, options) {
// In case we've been destroyed in the meantime
if (!this.document) {
return [];
}
@@ -146,16 +158,55 @@ class FontInspector {
* Handler for change in preview input.
*/
onPreviewFonts(value) {
this.store.dispatch(updatePreviewText(value));
this.update();
}
/**
+ * Handler for "ruleview-rule-selected" event emitted from the rule view when a rule is
+ * marked as selected for an editor.
+ * If selected for the font editor, hold a reference to the rule so we know where to
+ * put property changes coming from the font editor and show the font editor panel.
+ *
+ * @param {Object} eventData
+ * Data payload for the event. Contains:
+ * - {String} editorId - id of the editor for which the rule was selected
+ * - {Rule} rule - reference to rule that was selected
+ */
+ onRuleSelected(eventData) {
+ const { editorId, rule } = eventData;
+ if (editorId === FONT_EDITOR_ID) {
+ const selector = rule.matchedSelectors[0];
+ this.selectedRule = rule;
+ this.store.dispatch(toggleFontEditor(true, selector));
+ }
+ }
+
+ /**
+ * Handler for "ruleview-rule-unselected" event emitted from the rule view when a rule
+ * was released from being selected for an editor.
+ * If previously selected for the font editor, release the reference to the rule and
+ * hide the font editor panel.
+ *
+ * @param {Object} eventData
+ * Data payload for the event. Contains:
+ * - {String} editorId - id of the editor for which the rule was released
+ * - {Rule} rule - reference to rule that was released
+ */
+ onRuleUnselected(eventData) {
+ const { editorId, rule } = eventData;
+ if (editorId === FONT_EDITOR_ID && rule == this.selectedRule) {
+ this.selectedRule = null;
+ this.store.dispatch(toggleFontEditor(false));
+ }
+ }
+
+ /**
* Handler for the "theme-switched" event.
*/
onThemeChanged(frame) {
if (frame === this.document.defaultView) {
this.update();
}
}
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/fonts/reducers/font-editor.js
@@ -0,0 +1,33 @@
+/* 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_EDITOR_VISIBILITY,
+} = require("../actions/index");
+
+const INITIAL_STATE = {
+ // Whether or not the font editor is visible.
+ isVisible: false,
+ // Selector text of the rule where font properties will be written.
+ selector: "",
+};
+
+let reducers = {
+
+ [UPDATE_EDITOR_VISIBILITY](state, { isVisible, selector }) {
+ selector = isVisible ? selector : "";
+ return { ...state, isVisible, selector };
+ },
+
+};
+
+module.exports = function(state = INITIAL_STATE, action) {
+ let reducer = reducers[action.type];
+ if (!reducer) {
+ return state;
+ }
+ return reducer(state, action);
+};
--- a/devtools/client/inspector/fonts/reducers/moz.build
+++ b/devtools/client/inspector/fonts/reducers/moz.build
@@ -1,10 +1,11 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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(
+ 'font-editor.js',
'font-options.js',
'fonts.js',
)
--- a/devtools/client/inspector/fonts/types.js
+++ b/devtools/client/inspector/fonts/types.js
@@ -74,16 +74,27 @@ const font = exports.font = {
variationInstances: PropTypes.arrayOf(PropTypes.shape(fontVariationInstance))
};
exports.fontOptions = {
// The current preview text
previewText: PropTypes.string,
};
+exports.fontEditor = {
+ // Font currently being edited
+ font: PropTypes.shape(font),
+
+ // Whether or not the font editor is visible
+ isVisible: PropTypes.bool,
+
+ // Selector text of the rule where font properties will be written
+ selector: PropTypes.string,
+};
+
/**
* Font data.
*/
exports.fontData = {
// The fonts used in the current element.
fonts: PropTypes.arrayOf(PropTypes.shape(font)),
// Fonts used elsewhere.
--- a/devtools/client/inspector/reducers.js
+++ b/devtools/client/inspector/reducers.js
@@ -10,10 +10,11 @@
exports.animations = require("devtools/client/inspector/animation/reducers/animations");
exports.boxModel = require("devtools/client/inspector/boxmodel/reducers/box-model");
exports.changes = require("devtools/client/inspector/changes/reducers/changes");
exports.events = require("devtools/client/inspector/events/reducers/events");
exports.extensionsSidebar = require("devtools/client/inspector/extensions/reducers/sidebar");
exports.flexbox = require("devtools/client/inspector/flexbox/reducers/flexbox");
exports.fontOptions = require("devtools/client/inspector/fonts/reducers/font-options");
exports.fontData = require("devtools/client/inspector/fonts/reducers/fonts");
+exports.fontEditor = require("devtools/client/inspector/fonts/reducers/font-editor");
exports.grids = require("devtools/client/inspector/grids/reducers/grids");
exports.highlighterSettings = require("devtools/client/inspector/grids/reducers/highlighter-settings");
--- a/devtools/client/inspector/rules/rules.js
+++ b/devtools/client/inspector/rules/rules.js
@@ -105,17 +105,17 @@ function CssRuleView(inspector, document
this.inspector = inspector;
this.highlighters = inspector.highlighters;
this.styleDocument = document;
this.styleWindow = this.styleDocument.defaultView;
this.store = store || {};
// References to rules marked by various editors where they intend to write changes.
// @see selectRule(), unselectRule()
- this.selectedRules = {};
+ this.selectedRules = new Map();
this.pageStyle = pageStyle;
// Allow tests to override debouncing behavior, as this can cause intermittents.
this.debounce = debounce;
this.cssProperties = getCssProperties(inspector.toolbox);
this._outputParser = new OutputParser(document, this.cssProperties);
@@ -1221,80 +1221,84 @@ CssRuleView.prototype = {
* Key to use for collecting references to selected rules.
* @param {Boolean} [unselectOthers=true]
* Optional. Default: `true`. If true, unselect all other rules that were
* selected for the given editor. Ensures only one rule at a time is selected for
* a particular editor. Set to `false` if an editor may operate on multiple rules
* at a time.
*/
selectRule(rule, editorId, unselectOthers = true) {
- this.selectedRules[editorId] = this.getSelectedRules(editorId);
-
- if (!this.selectedRules[editorId].includes(rule)) {
- this.selectedRules[editorId].push(rule);
+ const rules = this.getSelectedRules(editorId);
+ if (!rules.includes(rule)) {
+ this.selectedRules.set(editorId, [...rules, rule]);
}
// Mark other rules for this editorId as unselected.
if (unselectOthers) {
- this.selectedRules[editorId]
+ rules
.filter(item => item !== rule)
.map(item => this.unselectRule(item, editorId));
}
this.emit("ruleview-rule-selected", {editorId, rule});
},
/**
* Unmark a rule as selected for the given editor id.
*
* @param {Rule} rule
* Rule object for which to remove the reference.
* @param {String} editorId
* Key for which to mark the given rule as selected.
*/
unselectRule(rule, editorId) {
- if (!Array.isArray(this.selectedRules[editorId])) {
+ const rules = this.selectedRules.get(editorId);
+ if (!Array.isArray(rules)) {
return;
}
- let index = this.selectedRules[editorId].findIndex(item => item === rule);
+ let index = rules.findIndex(item => item === rule);
if (index === -1) {
return;
}
- this.selectedRules[editorId].splice(index, 1);
+ rules.splice(index, 1);
+ this.selectedRules.set(editorId, rules);
this.emit("ruleview-rule-unselected", {editorId, rule});
},
/**
* Unmark all selected rules for all editors. If an editor id is provided, unmark all
* selected rules just for that editor leaving others untouched.
*
- * @param {String} [editorId]
+ * @param {String} editorId
* Optional editor id for which to restrict unselect operation.
*/
unselectAllRules(editorId) {
- let keys = Object.keys(this.selectedRules);
- keys = editorId ? keys.filter(key => (key === editorId)) : keys;
- for (let key of keys) {
- this.selectedRules[key].map(item => this.unselectRule(item, key));
+ for (let [id, rules] of this.selectedRules) {
+ // If we're supposed to unselect rules from just one editorId but it did not match,
+ // skip this iteration.
+ if (editorId && id !== editorId) {
+ continue;
+ }
+ rules.map(rule => this.unselectRule(rule, id));
}
},
/**
* Return an array of selected rules for the given editor id.
* If no rules match, return an empty arrary;
*
* @param {String} editorId
* Editor id for which to return selected rules.
* @return {Array}
*/
getSelectedRules(editorId) {
- return Array.isArray(this.selectedRules[editorId]) ?
- this.selectedRules[editorId] : [];
+ const rules = this.selectedRules.get(editorId);
+ return Array.isArray(rules) ? rules : [];
},
/**
* Called when a rule from the Rule view was marked as selected for an editor.
* Handle the event and show panels relevant for the given editor id.
*
* @param {Object} eventData
* Data payload for the event. Contains:
--- a/devtools/client/themes/fonts.css
+++ b/devtools/client/themes/fonts.css
@@ -5,16 +5,20 @@
#sidebar-panel-fontinspector {
margin: 0;
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
}
+#sidebar-panel-fonteditor {
+ padding: 1em;
+}
+
#font-container {
overflow: auto;
flex: auto;
}
.fonts-list {
padding: 0;
margin: 0;