зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1449885 - Read font variation axis data and setup UI with any axis values defined on rule. r=gl
MozReview-Commit-ID: 6tWRyjYcdDH
This commit is contained in:
Родитель
3680684f70
Коммит
0a3e54885d
|
@ -5,11 +5,20 @@
|
|||
"use strict";
|
||||
|
||||
const {
|
||||
RESET_EDITOR,
|
||||
UPDATE_AXIS_VALUE,
|
||||
UPDATE_EDITOR_VISIBILITY,
|
||||
UPDATE_EDITOR_STATE,
|
||||
} = require("./index");
|
||||
|
||||
module.exports = {
|
||||
|
||||
resetFontEditor() {
|
||||
return {
|
||||
type: RESET_EDITOR,
|
||||
};
|
||||
},
|
||||
|
||||
toggleFontEditor(isVisible, selector = "") {
|
||||
return {
|
||||
type: UPDATE_EDITOR_VISIBILITY,
|
||||
|
@ -18,4 +27,20 @@ module.exports = {
|
|||
};
|
||||
},
|
||||
|
||||
updateAxis(axis, value) {
|
||||
return {
|
||||
type: UPDATE_AXIS_VALUE,
|
||||
axis,
|
||||
value,
|
||||
};
|
||||
},
|
||||
|
||||
updateFontEditor(fonts, properties = {}) {
|
||||
return {
|
||||
type: UPDATE_EDITOR_STATE,
|
||||
fonts,
|
||||
properties,
|
||||
};
|
||||
},
|
||||
|
||||
};
|
||||
|
|
|
@ -8,6 +8,15 @@ const { createEnum } = require("devtools/client/shared/enum");
|
|||
|
||||
createEnum([
|
||||
|
||||
// Reset font editor to intial state.
|
||||
"RESET_EDITOR",
|
||||
|
||||
// Update the value of a variable font axis.
|
||||
"UPDATE_AXIS_VALUE",
|
||||
|
||||
// Update font editor with applicable fonts and user-defined CSS font properties.
|
||||
"UPDATE_EDITOR_STATE",
|
||||
|
||||
// Toggle the visibiltiy of the font editor
|
||||
"UPDATE_EDITOR_VISIBILITY",
|
||||
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
/* 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 dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
|
||||
class FontAxis extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
defaultValue: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
|
||||
label: PropTypes.string.isRequired,
|
||||
min: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
|
||||
max: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
|
||||
name: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
showInput: PropTypes.bool,
|
||||
step: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
|
||||
value: PropTypes.string,
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onChange = this.onChange.bind(this);
|
||||
}
|
||||
|
||||
onChange(e) {
|
||||
this.props.onChange(this.props.name, e.target.value);
|
||||
}
|
||||
|
||||
render() {
|
||||
const defaults = {
|
||||
min: this.props.min,
|
||||
max: this.props.max,
|
||||
onChange: this.onChange,
|
||||
step: this.props.step || 1,
|
||||
value: this.props.value || this.props.defaultValue,
|
||||
};
|
||||
|
||||
const range = dom.input(
|
||||
{
|
||||
...defaults,
|
||||
className: "font-axis-slider",
|
||||
title: this.props.label,
|
||||
type: "range",
|
||||
}
|
||||
);
|
||||
|
||||
const input = dom.input(
|
||||
{
|
||||
...defaults,
|
||||
className: "font-axis-input",
|
||||
type: "number",
|
||||
}
|
||||
);
|
||||
|
||||
return dom.label(
|
||||
{
|
||||
className: "font-axis",
|
||||
},
|
||||
dom.span(
|
||||
{
|
||||
className: "font-axis-label",
|
||||
},
|
||||
this.props.label
|
||||
),
|
||||
range,
|
||||
this.props.showInput ? input : null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FontAxis;
|
|
@ -4,27 +4,96 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { PureComponent } = require("devtools/client/shared/vendor/react");
|
||||
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 Types = require("../types");
|
||||
|
||||
class FontEditor extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
fontEditor: PropTypes.shape(Types.fontEditor).isRequired,
|
||||
onAxisUpdate: 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
|
||||
* Minumum value for range.
|
||||
* @param {Number|String} max
|
||||
* Maximum value for range.
|
||||
* @return {String}
|
||||
* Step value used in range input for font axis.
|
||||
*/
|
||||
getAxisStep(min, max) {
|
||||
let step = 1;
|
||||
let delta = parseInt(max, 10) - parseInt(min, 10);
|
||||
|
||||
if (delta <= 1) {
|
||||
step = 0.001;
|
||||
} else if (delta <= 10) {
|
||||
step = 0.01;
|
||||
} else if (delta <= 100) {
|
||||
step = 0.1;
|
||||
}
|
||||
|
||||
return step.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of FontAxis components for of the given variable font axis instances.
|
||||
* If an axis is defined in the fontEditor store, use its value, else use the default.
|
||||
*
|
||||
* @param {Array} fontAxes
|
||||
* Array of font axis instances
|
||||
* @param {Object} editedAxes
|
||||
* Object with axes and values edited by the user or predefined in the CSS
|
||||
* declaration for font-variation-settings.
|
||||
* @return {Array}
|
||||
* Array of FontAxis components
|
||||
*/
|
||||
renderAxes(fontAxes = [], editedAxes) {
|
||||
return fontAxes.map(axis => {
|
||||
return FontAxis({
|
||||
min: axis.minValue,
|
||||
max: axis.maxValue,
|
||||
value: editedAxes[axis.tag] || axis.defaultValue,
|
||||
step: this.getAxisStep(axis.minValue, axis.maxValue),
|
||||
label: axis.name,
|
||||
name: axis.tag,
|
||||
onChange: this.props.onAxisUpdate,
|
||||
showInput: true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 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 { selector } = this.props.fontEditor;
|
||||
const { fonts, axes } = 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;
|
||||
|
||||
return dom.div(
|
||||
{
|
||||
className: "theme-sidebar inspector-tabpanel",
|
||||
id: "sidebar-panel-fonteditor"
|
||||
}, `Placeholder for Font Editor panel for selector: ${selector}`
|
||||
id: "sidebar-panel-fontinspector"
|
||||
},
|
||||
fontAxes ?
|
||||
this.renderAxes(fontAxes, axes)
|
||||
:
|
||||
this.renderPlaceholder()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ class FontsApp extends PureComponent {
|
|||
fontData: PropTypes.shape(Types.fontData).isRequired,
|
||||
fontEditor: PropTypes.shape(Types.fontEditor).isRequired,
|
||||
fontOptions: PropTypes.shape(Types.fontOptions).isRequired,
|
||||
onAxisUpdate: PropTypes.func.isRequired,
|
||||
onPreviewFonts: PropTypes.func.isRequired,
|
||||
};
|
||||
}
|
||||
|
@ -29,7 +30,8 @@ class FontsApp extends PureComponent {
|
|||
fontData,
|
||||
fontEditor,
|
||||
fontOptions,
|
||||
onPreviewFonts
|
||||
onAxisUpdate,
|
||||
onPreviewFonts,
|
||||
} = this.props;
|
||||
|
||||
return dom.div(
|
||||
|
@ -40,6 +42,7 @@ class FontsApp extends PureComponent {
|
|||
fontEditor.isVisible ?
|
||||
FontEditor({
|
||||
fontEditor,
|
||||
onAxisUpdate,
|
||||
})
|
||||
:
|
||||
FontOverview({
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
DevToolsModules(
|
||||
'Font.js',
|
||||
'FontAxis.js',
|
||||
'FontEditor.js',
|
||||
'FontList.js',
|
||||
'FontOverview.js',
|
||||
|
|
|
@ -19,9 +19,18 @@ const INSPECTOR_L10N =
|
|||
|
||||
const { updateFonts } = require("./actions/fonts");
|
||||
const { updatePreviewText } = require("./actions/font-options");
|
||||
const { toggleFontEditor } = require("./actions/font-editor");
|
||||
const { resetFontEditor, toggleFontEditor, updateAxis, updateFontEditor } =
|
||||
require("./actions/font-editor");
|
||||
|
||||
const FONT_EDITOR_ID = "fonteditor";
|
||||
const FONT_PROPERTIES = [
|
||||
"font-optical-sizing",
|
||||
"font-size",
|
||||
"font-stretch",
|
||||
"font-style",
|
||||
"font-variation-settings",
|
||||
"font-weight",
|
||||
];
|
||||
|
||||
class FontInspector {
|
||||
constructor(inspector, window) {
|
||||
|
@ -33,6 +42,7 @@ class FontInspector {
|
|||
this.store = this.inspector.store;
|
||||
|
||||
this.update = this.update.bind(this);
|
||||
this.onAxisUpdate = this.onAxisUpdate.bind(this);
|
||||
this.onNewNode = this.onNewNode.bind(this);
|
||||
this.onPreviewFonts = this.onPreviewFonts.bind(this);
|
||||
this.onRuleSelected = this.onRuleSelected.bind(this);
|
||||
|
@ -49,6 +59,7 @@ class FontInspector {
|
|||
|
||||
let fontsApp = FontsApp({
|
||||
onPreviewFonts: this.onPreviewFonts,
|
||||
onAxisUpdate: this.onAxisUpdate,
|
||||
});
|
||||
|
||||
let provider = createElement(Provider, {
|
||||
|
@ -145,6 +156,19 @@ class FontInspector {
|
|||
this.inspector.sidebar.getCurrentTabID() === "fontinspector";
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for changes of font axis value. Updates the value in the store and previews
|
||||
* the change on the page.
|
||||
*
|
||||
* @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));
|
||||
}
|
||||
|
||||
/**
|
||||
* Selection 'new-node' event handler.
|
||||
*/
|
||||
|
@ -173,11 +197,13 @@ class FontInspector {
|
|||
* - {String} editorId - id of the editor for which the rule was selected
|
||||
* - {Rule} rule - reference to rule that was selected
|
||||
*/
|
||||
onRuleSelected(eventData) {
|
||||
async onRuleSelected(eventData) {
|
||||
const { editorId, rule } = eventData;
|
||||
if (editorId === FONT_EDITOR_ID) {
|
||||
const selector = rule.matchedSelectors[0];
|
||||
this.selectedRule = rule;
|
||||
|
||||
await this.refreshFontEditor();
|
||||
this.store.dispatch(toggleFontEditor(true, selector));
|
||||
}
|
||||
}
|
||||
|
@ -198,6 +224,7 @@ class FontInspector {
|
|||
if (editorId === FONT_EDITOR_ID && rule == this.selectedRule) {
|
||||
this.selectedRule = null;
|
||||
this.store.dispatch(toggleFontEditor(false));
|
||||
this.store.dispatch(resetFontEditor());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -210,6 +237,46 @@ class FontInspector {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the state of the font editor with:
|
||||
* - the fonts which apply to the current node;
|
||||
* - the CSS font properties declared on the selected rule.
|
||||
*
|
||||
* This method is called during initial setup and as a result of any property
|
||||
* values change in the Rule view. For the latter case, we do a deep compare between the
|
||||
* font properties on the selected rule and the ones already store to decide if to
|
||||
* update the font edtior to reflect a new external state.
|
||||
*/
|
||||
async refreshFontEditor() {
|
||||
if (!this.selectedRule || !this.inspector || !this.store) {
|
||||
return;
|
||||
}
|
||||
|
||||
const options = {};
|
||||
if (this.pageStyle.supportsFontVariations) {
|
||||
options.includeVariations = true;
|
||||
}
|
||||
|
||||
const node = this.inspector.selection.nodeFront;
|
||||
const fonts = await this.getFontsForNode(node, options);
|
||||
// Collect any expected font properties and their values from the selected rule.
|
||||
const properties = this.selectedRule.textProps.reduce((acc, prop) => {
|
||||
if (FONT_PROPERTIES.includes(prop.name)) {
|
||||
acc[prop.name] = prop.value;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const fontEditor = this.store.getState().fontEditor;
|
||||
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
|
||||
async update() {
|
||||
// Stop refreshing if the inspector or store is already destroyed.
|
||||
if (!this.inspector || !this.store) {
|
||||
|
|
|
@ -5,18 +5,58 @@
|
|||
"use strict";
|
||||
|
||||
const {
|
||||
RESET_EDITOR,
|
||||
UPDATE_AXIS_VALUE,
|
||||
UPDATE_EDITOR_STATE,
|
||||
UPDATE_EDITOR_VISIBILITY,
|
||||
} = require("../actions/index");
|
||||
|
||||
const INITIAL_STATE = {
|
||||
// Variable font axes.
|
||||
axes: {},
|
||||
// Fonts applicable to selected element.
|
||||
fonts: [],
|
||||
// Whether or not the font editor is visible.
|
||||
isVisible: false,
|
||||
// Selector text of the rule where font properties will be written.
|
||||
// CSS font properties defined on the selected rule.
|
||||
properties: {},
|
||||
// Selector text of the selected rule where updated font properties will be written.
|
||||
selector: "",
|
||||
};
|
||||
|
||||
let reducers = {
|
||||
|
||||
[RESET_EDITOR](state) {
|
||||
return { ...INITIAL_STATE };
|
||||
},
|
||||
|
||||
[UPDATE_AXIS_VALUE](state, { axis, value }) {
|
||||
let newState = { ...state };
|
||||
newState.axes[axis] = value;
|
||||
return newState;
|
||||
},
|
||||
|
||||
[UPDATE_EDITOR_STATE](state, { fonts, properties }) {
|
||||
let axes = {};
|
||||
|
||||
if (properties["font-variation-settings"]) {
|
||||
// Parse font-variation-settings CSS declaration into an object
|
||||
// with axis tags as keys and axis values as values.
|
||||
axes = properties["font-variation-settings"]
|
||||
.split(",")
|
||||
.reduce((acc, pair) => {
|
||||
// Tags are always in quotes. Split by quote and filter excessive whitespace.
|
||||
pair = pair.split(/["']/).filter(part => part.trim() !== "");
|
||||
const tag = pair[0].trim();
|
||||
const value = pair[1].trim();
|
||||
acc[tag] = value;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
return { ...state, axes, fonts, properties };
|
||||
},
|
||||
|
||||
[UPDATE_EDITOR_VISIBILITY](state, { isVisible, selector }) {
|
||||
return { ...state, isVisible, selector };
|
||||
},
|
||||
|
|
|
@ -80,12 +80,18 @@ exports.fontOptions = {
|
|||
};
|
||||
|
||||
exports.fontEditor = {
|
||||
// Font currently being edited
|
||||
font: PropTypes.shape(font),
|
||||
// Variable font axes and their values
|
||||
axes: PropTypes.object,
|
||||
|
||||
// Fonts applicable to selected element
|
||||
fonts: PropTypes.arrayOf(PropTypes.shape(font)),
|
||||
|
||||
// Whether or not the font editor is visible
|
||||
isVisible: PropTypes.bool,
|
||||
|
||||
// CSS font properties defined on the element
|
||||
properties: PropTypes.object,
|
||||
|
||||
// Selector text of the rule where font properties will be written
|
||||
selector: PropTypes.string,
|
||||
};
|
||||
|
|
|
@ -8,14 +8,10 @@
|
|||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#sidebar-panel-fonteditor {
|
||||
padding: 1em;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#font-container {
|
||||
overflow: auto;
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
|
@ -113,6 +109,40 @@
|
|||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.font-axis {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 5px 20px;
|
||||
}
|
||||
|
||||
.font-axis-input {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.font-axis-label {
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
.font-axis-slider {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.font-axis-slider::-moz-focus-outer {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.font-axis-slider::-moz-range-thumb {
|
||||
background: var(--grey-50);
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.font-axis-slider:focus::-moz-range-thumb {
|
||||
background: var(--blue-55);
|
||||
}
|
||||
|
||||
.font-origin {
|
||||
margin-top: .2em;
|
||||
color: var(--grey-50);
|
||||
|
|
Загрузка…
Ссылка в новой задаче