summaryrefslogtreecommitdiffstats
path: root/devtools/client/inspector/fonts/components/FontEditor.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--devtools/client/inspector/fonts/components/FontEditor.js357
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;