diff options
Diffstat (limited to 'devtools/client/inspector/fonts/components/FontEditor.js')
-rw-r--r-- | devtools/client/inspector/fonts/components/FontEditor.js | 357 |
1 files changed, 357 insertions, 0 deletions
diff --git a/devtools/client/inspector/fonts/components/FontEditor.js b/devtools/client/inspector/fonts/components/FontEditor.js new file mode 100644 index 0000000000..b98118c9fc --- /dev/null +++ b/devtools/client/inspector/fonts/components/FontEditor.js @@ -0,0 +1,357 @@ +/* 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("resource://devtools/client/shared/vendor/react.js"); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); +const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); + +const FontAxis = createFactory( + require("resource://devtools/client/inspector/fonts/components/FontAxis.js") +); +const FontName = createFactory( + require("resource://devtools/client/inspector/fonts/components/FontName.js") +); +const FontSize = createFactory( + require("resource://devtools/client/inspector/fonts/components/FontSize.js") +); +const FontStyle = createFactory( + require("resource://devtools/client/inspector/fonts/components/FontStyle.js") +); +const FontWeight = createFactory( + require("resource://devtools/client/inspector/fonts/components/FontWeight.js") +); +const LetterSpacing = createFactory( + require("resource://devtools/client/inspector/fonts/components/LetterSpacing.js") +); +const LineHeight = createFactory( + require("resource://devtools/client/inspector/fonts/components/LineHeight.js") +); + +const { + getStr, +} = require("resource://devtools/client/inspector/fonts/utils/l10n.js"); +const Types = require("resource://devtools/client/inspector/fonts/types.js"); + +// Maximum number of font families to be shown by default. Any others will be hidden +// under a collapsed <details> element with a toggle to reveal them. +const MAX_FONTS = 3; + +class FontEditor extends PureComponent { + static get propTypes() { + return { + fontEditor: PropTypes.shape(Types.fontEditor).isRequired, + onInstanceChange: PropTypes.func.isRequired, + onPropertyChange: PropTypes.func.isRequired, + onToggleFontHighlight: PropTypes.func.isRequired, + }; + } + + /** + * Get an array of FontAxis components with editing controls for of the given variable + * font axes. If no axes were given, return null. + * If an axis' value was declared on the font-variation-settings CSS property or was + * changed using the font editor, use that value, otherwise use the axis default. + * + * @param {Array} fontAxes + * Array of font axis instances + * @param {Object} editedAxes + * Object with axes and values edited by the user or defined in the CSS + * declaration for font-variation-settings. + * @return {Array|null} + */ + renderAxes(fontAxes = [], editedAxes) { + if (!fontAxes.length) { + return null; + } + + return fontAxes.map(axis => { + return FontAxis({ + key: axis.tag, + axis, + disabled: this.props.fontEditor.disabled, + onChange: this.props.onPropertyChange, + minLabel: true, + maxLabel: true, + value: editedAxes[axis.tag] || axis.defaultValue, + }); + }); + } + + /** + * Render fonts used on the selected node grouped by font-family. + * + * @param {Array} fonts + * Fonts used on selected node. + * @return {DOMNode} + */ + renderUsedFonts(fonts) { + if (!fonts.length) { + return null; + } + + // Group fonts by family name. + const fontGroups = fonts.reduce((acc, font) => { + const family = font.CSSFamilyName.toString(); + acc[family] = acc[family] || []; + acc[family].push(font); + return acc; + }, {}); + + const renderedFontGroups = Object.keys(fontGroups).map(family => { + return this.renderFontGroup(family, fontGroups[family]); + }); + + const topFontsList = renderedFontGroups.slice(0, MAX_FONTS); + const moreFontsList = renderedFontGroups.slice( + MAX_FONTS, + renderedFontGroups.length + ); + + const moreFonts = !moreFontsList.length + ? null + : dom.details( + {}, + dom.summary( + {}, + dom.span( + { className: "label-open" }, + getStr("fontinspector.showMore") + ), + dom.span( + { className: "label-close" }, + getStr("fontinspector.showLess") + ) + ), + moreFontsList + ); + + return dom.label( + { + className: "font-control font-control-used-fonts", + }, + dom.span( + { + className: "font-control-label", + }, + getStr("fontinspector.fontsUsedLabel") + ), + dom.div( + { + className: "font-control-box", + }, + topFontsList, + moreFonts + ) + ); + } + + renderFontGroup(family, fonts = []) { + const group = fonts.map(font => { + return FontName({ + key: font.name, + font, + onToggleFontHighlight: this.props.onToggleFontHighlight, + }); + }); + + return dom.div( + { + key: family, + className: "font-group", + }, + dom.div( + { + className: "font-family-name", + }, + family + ), + group + ); + } + + renderFontSize(value) { + return ( + value !== null && + FontSize({ + key: `${this.props.fontEditor.id}:font-size`, + disabled: this.props.fontEditor.disabled, + onChange: this.props.onPropertyChange, + value, + }) + ); + } + + renderLineHeight(value) { + return ( + value !== null && + LineHeight({ + key: `${this.props.fontEditor.id}:line-height`, + disabled: this.props.fontEditor.disabled, + onChange: this.props.onPropertyChange, + value, + }) + ); + } + + renderLetterSpacing(value) { + return ( + value !== null && + LetterSpacing({ + key: `${this.props.fontEditor.id}:letter-spacing`, + disabled: this.props.fontEditor.disabled, + onChange: this.props.onPropertyChange, + value, + }) + ); + } + + renderFontStyle(value) { + return ( + value && + FontStyle({ + onChange: this.props.onPropertyChange, + disabled: this.props.fontEditor.disabled, + value, + }) + ); + } + + renderFontWeight(value) { + return ( + value !== null && + FontWeight({ + onChange: this.props.onPropertyChange, + disabled: this.props.fontEditor.disabled, + value, + }) + ); + } + + /** + * 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( + { + key: instance.name, + value: instance.name, + }, + instance.name + ) + ); + + // Generate the dropdown. + const instanceSelect = dom.select( + { + className: "font-control-input font-value-select", + value: selectedInstance.name || customInstance.name, + 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", + }, + getStr("fontinspector.fontInstanceLabel") + ), + instanceSelect + ); + } + + renderWarning(warning) { + return dom.div( + { + id: "font-editor", + }, + dom.div( + { + className: "devtools-sidepanel-no-result", + }, + warning + ) + ); + } + + render() { + const { fontEditor } = this.props; + const { fonts, axes, instance, properties, warning } = fontEditor; + // Pick the first font to show editor controls regardless of how many fonts are used. + const font = fonts[0]; + const hasFontAxes = font?.variationAxes; + const hasFontInstances = font?.variationInstances?.length > 0; + const hasSlantOrItalicAxis = font?.variationAxes?.find(axis => { + return axis.tag === "slnt" || axis.tag === "ital"; + }); + const hasWeightAxis = font?.variationAxes?.find(axis => { + return axis.tag === "wght"; + }); + + // Show the empty state with a warning message when a used font was not found. + if (!font) { + return this.renderWarning(warning); + } + + return dom.div( + { + id: "font-editor", + }, + // Always render UI for used fonts. + this.renderUsedFonts(fonts), + // Render UI for font variation instances if they are defined. + hasFontInstances && + this.renderInstances(font.variationInstances, instance), + // Always render UI for font size. + this.renderFontSize(properties["font-size"]), + // Always render UI for line height. + this.renderLineHeight(properties["line-height"]), + // Always render UI for letter spacing. + this.renderLetterSpacing(properties["letter-spacing"]), + // Render UI for font weight if no "wght" registered axis is defined. + !hasWeightAxis && this.renderFontWeight(properties["font-weight"]), + // Render UI for font style if no "slnt" or "ital" registered axis is defined. + !hasSlantOrItalicAxis && this.renderFontStyle(properties["font-style"]), + // Render UI for each variable font axis if any are defined. + hasFontAxes && this.renderAxes(font.variationAxes, axes) + ); + } +} + +module.exports = FontEditor; |