зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1333561 - Part 3: Implements the box model panel in the layout view. r=jdescottes
This commit is contained in:
Родитель
e8c8117123
Коммит
f1d7bb9aba
|
@ -13,6 +13,7 @@
|
|||
<link rel="stylesheet" href="chrome://devtools/skin/rules.css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/skin/computed.css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/skin/fonts.css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/skin/boxmodel.css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/skin/deprecated-boxmodel.css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/skin/layout.css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/skin/animationinspector.css"/>
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/* 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_LAYOUT,
|
||||
} = require("./index");
|
||||
|
||||
module.exports = {
|
||||
|
||||
/**
|
||||
* Update the layout state with the new layout properties.
|
||||
*/
|
||||
updateLayout(layout) {
|
||||
return {
|
||||
type: UPDATE_LAYOUT,
|
||||
layout,
|
||||
};
|
||||
},
|
||||
|
||||
};
|
|
@ -14,6 +14,9 @@ createEnum([
|
|||
// Update the entire grids state with the new list of grids.
|
||||
"UPDATE_GRIDS",
|
||||
|
||||
// Update the layout state with the latest layout properties.
|
||||
"UPDATE_LAYOUT",
|
||||
|
||||
// Update the grid highlighter's show grid line numbers state.
|
||||
"UPDATE_SHOW_GRID_LINE_NUMBERS",
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
'box-model.js',
|
||||
'grids.js',
|
||||
'highlighter-settings.js',
|
||||
'index.js',
|
||||
|
|
|
@ -8,19 +8,29 @@ const { addons, createClass, createFactory, DOM: dom, PropTypes } =
|
|||
require("devtools/client/shared/vendor/react");
|
||||
const { connect } = require("devtools/client/shared/vendor/react-redux");
|
||||
|
||||
const { LocalizationHelper } = require("devtools/shared/l10n");
|
||||
|
||||
const Accordion = createFactory(require("./Accordion"));
|
||||
const BoxModel = createFactory(require("./BoxModel"));
|
||||
const Grid = createFactory(require("./Grid"));
|
||||
|
||||
const Types = require("../types");
|
||||
const { getStr } = require("../utils/l10n");
|
||||
|
||||
const BOXMODEL_STRINGS_URI = "devtools/client/locales/boxmodel.properties";
|
||||
const BOXMODEL_L10N = new LocalizationHelper(BOXMODEL_STRINGS_URI);
|
||||
|
||||
const App = createClass({
|
||||
|
||||
displayName: "App",
|
||||
|
||||
propTypes: {
|
||||
boxModel: PropTypes.shape(Types.boxModel).isRequired,
|
||||
grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired,
|
||||
highlighterSettings: PropTypes.shape(Types.highlighterSettings).isRequired,
|
||||
onShowBoxModelEditor: PropTypes.func.isRequired,
|
||||
onHideBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onToggleGridHighlighter: PropTypes.func.isRequired,
|
||||
onToggleShowGridLineNumbers: PropTypes.func.isRequired,
|
||||
onToggleShowInfiniteLines: PropTypes.func.isRequired,
|
||||
|
@ -35,12 +45,18 @@ const App = createClass({
|
|||
},
|
||||
Accordion({
|
||||
items: [
|
||||
{
|
||||
header: BOXMODEL_L10N.getStr("boxmodel.title"),
|
||||
component: BoxModel,
|
||||
componentProps: this.props,
|
||||
opened: true,
|
||||
},
|
||||
{
|
||||
header: getStr("layout.header"),
|
||||
component: Grid,
|
||||
componentProps: this.props,
|
||||
opened: true
|
||||
}
|
||||
opened: true,
|
||||
},
|
||||
]
|
||||
})
|
||||
);
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/* 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 { addons, createClass, createFactory, DOM: dom, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
|
||||
const BoxModelInfo = createFactory(require("./BoxModelInfo"));
|
||||
const BoxModelMain = createFactory(require("./BoxModelMain"));
|
||||
|
||||
const Types = require("../types");
|
||||
|
||||
module.exports = createClass({
|
||||
|
||||
displayName: "BoxModel",
|
||||
|
||||
propTypes: {
|
||||
boxModel: PropTypes.shape(Types.boxModel).isRequired,
|
||||
onHideBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onShowBoxModelEditor: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
mixins: [ addons.PureRenderMixin ],
|
||||
|
||||
render() {
|
||||
let {
|
||||
boxModel,
|
||||
onHideBoxModelHighlighter,
|
||||
onShowBoxModelEditor,
|
||||
onShowBoxModelHighlighter,
|
||||
} = this.props;
|
||||
|
||||
return dom.div(
|
||||
{
|
||||
className: "boxmodel-container",
|
||||
},
|
||||
BoxModelMain({
|
||||
boxModel,
|
||||
onHideBoxModelHighlighter,
|
||||
onShowBoxModelEditor,
|
||||
onShowBoxModelHighlighter,
|
||||
}),
|
||||
BoxModelInfo({
|
||||
boxModel,
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
});
|
|
@ -0,0 +1,65 @@
|
|||
/* 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 { addons, createClass, DOM: dom, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
const { editableItem } = require("devtools/client/shared/inplace-editor");
|
||||
|
||||
const LONG_TEXT_ROTATE_LIMIT = 3;
|
||||
|
||||
module.exports = createClass({
|
||||
|
||||
displayName: "BoxModelEditable",
|
||||
|
||||
propTypes: {
|
||||
box: PropTypes.string.isRequired,
|
||||
direction: PropTypes.string.isRequired,
|
||||
property: PropTypes.string.isRequired,
|
||||
textContent: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||
onShowBoxModelEditor: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
mixins: [ addons.PureRenderMixin ],
|
||||
|
||||
componentDidMount() {
|
||||
let { property, onShowBoxModelEditor } = this.props;
|
||||
|
||||
editableItem({
|
||||
element: this.refs.span,
|
||||
}, (element, event) => {
|
||||
onShowBoxModelEditor(element, event, property);
|
||||
});
|
||||
},
|
||||
|
||||
render() {
|
||||
let {
|
||||
box,
|
||||
direction,
|
||||
property,
|
||||
textContent,
|
||||
} = this.props;
|
||||
|
||||
let rotate = (direction == "left" || direction == "right") &&
|
||||
textContent.toString().length > LONG_TEXT_ROTATE_LIMIT;
|
||||
|
||||
return dom.p(
|
||||
{
|
||||
className: `boxmodel-${box} boxmodel-${direction}
|
||||
${rotate ? "boxmodel-rotate" : ""}`,
|
||||
},
|
||||
dom.span(
|
||||
{
|
||||
className: "boxmodel-editable",
|
||||
"data-box": box,
|
||||
title: property,
|
||||
ref: "span",
|
||||
},
|
||||
textContent
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
});
|
|
@ -0,0 +1,62 @@
|
|||
/* 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 { LocalizationHelper } = require("devtools/shared/l10n");
|
||||
const { addons, createClass, DOM: dom, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
|
||||
const Types = require("../types");
|
||||
|
||||
const BOXMODEL_STRINGS_URI = "devtools/client/locales/boxmodel.properties";
|
||||
const BOXMODEL_L10N = new LocalizationHelper(BOXMODEL_STRINGS_URI);
|
||||
|
||||
const SHARED_STRINGS_URI = "devtools/client/locales/shared.properties";
|
||||
const SHARED_L10N = new LocalizationHelper(SHARED_STRINGS_URI);
|
||||
|
||||
module.exports = createClass({
|
||||
|
||||
displayName: "BoxModelInfo",
|
||||
|
||||
propTypes: {
|
||||
boxModel: PropTypes.shape(Types.boxModel).isRequired,
|
||||
},
|
||||
|
||||
mixins: [ addons.PureRenderMixin ],
|
||||
|
||||
render() {
|
||||
let { boxModel } = this.props;
|
||||
let { layout } = boxModel;
|
||||
let { width, height, position } = layout;
|
||||
|
||||
return dom.div(
|
||||
{
|
||||
className: "boxmodel-info",
|
||||
},
|
||||
dom.span(
|
||||
{
|
||||
className: "boxmodel-element-size",
|
||||
},
|
||||
SHARED_L10N.getFormatStr("dimensions", width, height)
|
||||
),
|
||||
dom.section(
|
||||
{
|
||||
className: "boxmodel-position-group",
|
||||
},
|
||||
dom.button({
|
||||
className: "layout-geometry-editor devtools-button",
|
||||
title: BOXMODEL_L10N.getStr("boxmodel.geometryButton.tooltip"),
|
||||
}),
|
||||
dom.span(
|
||||
{
|
||||
className: "boxmodel-element-position",
|
||||
},
|
||||
position
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
});
|
|
@ -0,0 +1,261 @@
|
|||
/* 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 { addons, createClass, createFactory, DOM: dom, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
|
||||
const { LocalizationHelper } = require("devtools/shared/l10n");
|
||||
|
||||
const BoxModelEditable = createFactory(require("./BoxModelEditable"));
|
||||
|
||||
const Types = require("../types");
|
||||
|
||||
const BOXMODEL_STRINGS_URI = "devtools/client/locales/boxmodel.properties";
|
||||
const BOXMODEL_L10N = new LocalizationHelper(BOXMODEL_STRINGS_URI);
|
||||
|
||||
const SHARED_STRINGS_URI = "devtools/client/locales/shared.properties";
|
||||
const SHARED_L10N = new LocalizationHelper(SHARED_STRINGS_URI);
|
||||
|
||||
module.exports = createClass({
|
||||
|
||||
displayName: "BoxModelMain",
|
||||
|
||||
propTypes: {
|
||||
boxModel: PropTypes.shape(Types.boxModel).isRequired,
|
||||
onHideBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onShowBoxModelEditor: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
mixins: [ addons.PureRenderMixin ],
|
||||
|
||||
getBorderOrPaddingValue(property) {
|
||||
let { layout } = this.props.boxModel;
|
||||
return layout[property] ? parseFloat(layout[property]) : "-";
|
||||
},
|
||||
|
||||
getHeightOrWidthValue(property) {
|
||||
let { layout } = this.props.boxModel;
|
||||
|
||||
if (property == undefined) {
|
||||
return "-";
|
||||
}
|
||||
|
||||
property -= parseFloat(layout["border-left-width"]) +
|
||||
parseFloat(layout["border-right-width"]) +
|
||||
parseFloat(layout["padding-left"]) +
|
||||
parseFloat(layout["padding-right"]);
|
||||
property = parseFloat(property.toPrecision(6));
|
||||
|
||||
return property;
|
||||
},
|
||||
|
||||
getMarginValue(property, direction) {
|
||||
let { layout } = this.props.boxModel;
|
||||
let autoMargins = layout.autoMargins || {};
|
||||
let value = "-";
|
||||
|
||||
if (direction in autoMargins) {
|
||||
value = "auto";
|
||||
} else if (layout[property]) {
|
||||
value = parseFloat(layout[property]);
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
|
||||
onHighlightMouseOver(event) {
|
||||
let region = event.target.getAttribute("data-box");
|
||||
if (!region) {
|
||||
this.props.onHideBoxModelHighlighter();
|
||||
}
|
||||
|
||||
this.props.onShowBoxModelHighlighter({
|
||||
region,
|
||||
showOnly: region,
|
||||
onlyRegionArea: true,
|
||||
});
|
||||
},
|
||||
|
||||
render() {
|
||||
let { boxModel, onShowBoxModelEditor } = this.props;
|
||||
let { layout } = boxModel;
|
||||
let { width, height } = layout;
|
||||
|
||||
let borderTop = this.getBorderOrPaddingValue("border-top-width");
|
||||
let borderRight = this.getBorderOrPaddingValue("border-right-width");
|
||||
let borderBottom = this.getBorderOrPaddingValue("border-bottom-width");
|
||||
let borderLeft = this.getBorderOrPaddingValue("border-left-width");
|
||||
|
||||
let paddingTop = this.getBorderOrPaddingValue("padding-top");
|
||||
let paddingRight = this.getBorderOrPaddingValue("padding-right");
|
||||
let paddingBottom = this.getBorderOrPaddingValue("padding-bottom");
|
||||
let paddingLeft = this.getBorderOrPaddingValue("padding-left");
|
||||
|
||||
let marginTop = this.getMarginValue("margin-top", "top");
|
||||
let marginRight = this.getMarginValue("margin-right", "right");
|
||||
let marginBottom = this.getMarginValue("margin-bottom", "bottom");
|
||||
let marginLeft = this.getMarginValue("margin-left", "left");
|
||||
|
||||
width = this.getHeightOrWidthValue(width);
|
||||
height = this.getHeightOrWidthValue(height);
|
||||
|
||||
return dom.div(
|
||||
{
|
||||
className: "boxmodel-main",
|
||||
onMouseOver: this.onHighlightMouseOver,
|
||||
onMouseOut: this.props.onHideBoxModelHighlighter,
|
||||
},
|
||||
dom.span(
|
||||
{
|
||||
className: "boxmodel-legend",
|
||||
"data-box": "margin",
|
||||
title: BOXMODEL_L10N.getStr("boxmodel.margin"),
|
||||
},
|
||||
BOXMODEL_L10N.getStr("boxmodel.margin")
|
||||
),
|
||||
dom.div(
|
||||
{
|
||||
className: "boxmodel-margins",
|
||||
"data-box": "margin",
|
||||
title: BOXMODEL_L10N.getStr("boxmodel.margin"),
|
||||
},
|
||||
dom.span(
|
||||
{
|
||||
className: "boxmodel-legend",
|
||||
"data-box": "border",
|
||||
title: BOXMODEL_L10N.getStr("boxmodel.border"),
|
||||
},
|
||||
BOXMODEL_L10N.getStr("boxmodel.border")
|
||||
),
|
||||
dom.div(
|
||||
{
|
||||
className: "boxmodel-borders",
|
||||
"data-box": "border",
|
||||
title: BOXMODEL_L10N.getStr("boxmodel.border"),
|
||||
},
|
||||
dom.span(
|
||||
{
|
||||
className: "boxmodel-legend",
|
||||
"data-box": "padding",
|
||||
title: BOXMODEL_L10N.getStr("boxmodel.padding"),
|
||||
},
|
||||
BOXMODEL_L10N.getStr("boxmodel.padding")
|
||||
),
|
||||
dom.div(
|
||||
{
|
||||
className: "boxmodel-paddings",
|
||||
"data-box": "padding",
|
||||
title: BOXMODEL_L10N.getStr("boxmodel.padding"),
|
||||
},
|
||||
dom.div({
|
||||
className: "boxmodel-content",
|
||||
"data-box": "content",
|
||||
title: BOXMODEL_L10N.getStr("boxmodel.content"),
|
||||
})
|
||||
)
|
||||
),
|
||||
),
|
||||
BoxModelEditable({
|
||||
box: "margin",
|
||||
direction: "top",
|
||||
property: "margin-top",
|
||||
textContent: marginTop,
|
||||
onShowBoxModelEditor,
|
||||
}),
|
||||
BoxModelEditable({
|
||||
box: "margin",
|
||||
direction: "right",
|
||||
property: "margin-right",
|
||||
textContent: marginRight,
|
||||
onShowBoxModelEditor,
|
||||
}),
|
||||
BoxModelEditable({
|
||||
box: "margin",
|
||||
direction: "bottom",
|
||||
property: "margin-bottom",
|
||||
textContent: marginBottom,
|
||||
onShowBoxModelEditor,
|
||||
}),
|
||||
BoxModelEditable({
|
||||
box: "margin",
|
||||
direction: "left",
|
||||
property: "margin-left",
|
||||
textContent: marginLeft,
|
||||
onShowBoxModelEditor,
|
||||
}),
|
||||
BoxModelEditable({
|
||||
box: "border",
|
||||
direction: "top",
|
||||
property: "border-top-width",
|
||||
textContent: borderTop,
|
||||
onShowBoxModelEditor,
|
||||
}),
|
||||
BoxModelEditable({
|
||||
box: "border",
|
||||
direction: "right",
|
||||
property: "border-right-width",
|
||||
textContent: borderRight,
|
||||
onShowBoxModelEditor,
|
||||
}),
|
||||
BoxModelEditable({
|
||||
box: "border",
|
||||
direction: "bottom",
|
||||
property: "border-bottom-width",
|
||||
textContent: borderBottom,
|
||||
onShowBoxModelEditor,
|
||||
}),
|
||||
BoxModelEditable({
|
||||
box: "border",
|
||||
direction: "left",
|
||||
property: "border-left-width",
|
||||
textContent: borderLeft,
|
||||
onShowBoxModelEditor,
|
||||
}),
|
||||
BoxModelEditable({
|
||||
box: "padding",
|
||||
direction: "top",
|
||||
property: "padding-top",
|
||||
textContent: paddingTop,
|
||||
onShowBoxModelEditor,
|
||||
}),
|
||||
BoxModelEditable({
|
||||
box: "padding",
|
||||
direction: "right",
|
||||
property: "padding-right",
|
||||
textContent: paddingRight,
|
||||
onShowBoxModelEditor,
|
||||
}),
|
||||
BoxModelEditable({
|
||||
box: "padding",
|
||||
direction: "bottom",
|
||||
property: "padding-bottom",
|
||||
textContent: paddingBottom,
|
||||
onShowBoxModelEditor,
|
||||
}),
|
||||
BoxModelEditable({
|
||||
box: "padding",
|
||||
direction: "left",
|
||||
property: "padding-left",
|
||||
textContent: paddingLeft,
|
||||
onShowBoxModelEditor,
|
||||
}),
|
||||
dom.p(
|
||||
{
|
||||
className: "boxmodel-size",
|
||||
},
|
||||
dom.span(
|
||||
{
|
||||
"data-box": "content",
|
||||
title: BOXMODEL_L10N.getStr("boxmodel.content"),
|
||||
},
|
||||
SHARED_L10N.getFormatStr("dimensions", width, height)
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
});
|
|
@ -8,6 +8,10 @@ DevToolsModules(
|
|||
'Accordion.css',
|
||||
'Accordion.js',
|
||||
'App.js',
|
||||
'BoxModel.js',
|
||||
'BoxModelEditable.js',
|
||||
'BoxModelInfo.js',
|
||||
'BoxModelMain.js',
|
||||
'Grid.js',
|
||||
'GridDisplaySettings.js',
|
||||
'GridList.js',
|
||||
|
|
|
@ -6,9 +6,16 @@
|
|||
|
||||
const Services = require("Services");
|
||||
const { Task } = require("devtools/shared/task");
|
||||
const { getCssProperties } = require("devtools/shared/fronts/css-properties");
|
||||
const { ReflowFront } = require("devtools/shared/fronts/reflow");
|
||||
|
||||
const { InplaceEditor } = require("devtools/client/shared/inplace-editor");
|
||||
const { createFactory, createElement } = require("devtools/client/shared/vendor/react");
|
||||
const { Provider } = require("devtools/client/shared/vendor/react-redux");
|
||||
|
||||
const {
|
||||
updateLayout,
|
||||
} = require("./actions/box-model");
|
||||
const {
|
||||
updateGridHighlighted,
|
||||
updateGrids,
|
||||
|
@ -21,10 +28,13 @@ const {
|
|||
const App = createFactory(require("./components/App"));
|
||||
const Store = require("./store");
|
||||
|
||||
const EditingSession = require("./utils/editing-session");
|
||||
|
||||
const { LocalizationHelper } = require("devtools/shared/l10n");
|
||||
const INSPECTOR_L10N =
|
||||
new LocalizationHelper("devtools/client/locales/inspector.properties");
|
||||
|
||||
const NUMERIC = /^-?[\d\.]+$/;
|
||||
const SHOW_GRID_LINE_NUMBERS = "devtools.gridinspector.showGridLineNumbers";
|
||||
const SHOW_INFINITE_LINES_PREF = "devtools.gridinspector.showInfiniteLines";
|
||||
|
||||
|
@ -35,15 +45,19 @@ function LayoutView(inspector, window) {
|
|||
this.store = null;
|
||||
this.walker = this.inspector.walker;
|
||||
|
||||
this.updateBoxModel = this.updateBoxModel.bind(this);
|
||||
|
||||
this.onGridLayoutChange = this.onGridLayoutChange.bind(this);
|
||||
this.onHighlighterChange = this.onHighlighterChange.bind(this);
|
||||
this.onNewSelection = this.onNewSelection.bind(this);
|
||||
this.onSidebarSelect = this.onSidebarSelect.bind(this);
|
||||
|
||||
this.init();
|
||||
|
||||
this.highlighters.on("grid-highlighter-hidden", this.onHighlighterChange);
|
||||
this.highlighters.on("grid-highlighter-shown", this.onHighlighterChange);
|
||||
this.inspector.selection.on("new-node-front", this.onNewSelection);
|
||||
this.inspector.sidebar.on("select", this.onSidebarSelect);
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
LayoutView.prototype = {
|
||||
|
@ -63,6 +77,95 @@ LayoutView.prototype = {
|
|||
this.loadHighlighterSettings();
|
||||
|
||||
let app = App({
|
||||
/**
|
||||
* Hides the box-model highlighter on the currently selected element.
|
||||
*/
|
||||
onHideBoxModelHighlighter: () => {
|
||||
let toolbox = this.inspector.toolbox;
|
||||
toolbox.highlighterUtils.unhighlight();
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows the inplace editor when a box model editable value is clicked on the
|
||||
* box model panel.
|
||||
*
|
||||
* @param {DOMNode} element
|
||||
* The element that was clicked.
|
||||
* @param {Event} event
|
||||
* The event object.
|
||||
* @param {String} property
|
||||
* The name of the property.
|
||||
*/
|
||||
onShowBoxModelEditor: (element, event, property) => {
|
||||
let session = new EditingSession({
|
||||
inspector: this.inspector,
|
||||
doc: this.document,
|
||||
elementRules: this.elementRules,
|
||||
});
|
||||
let initialValue = session.getProperty(property);
|
||||
|
||||
let editor = new InplaceEditor({
|
||||
element: element,
|
||||
initial: initialValue,
|
||||
contentType: InplaceEditor.CONTENT_TYPES.CSS_VALUE,
|
||||
property: {
|
||||
name: property
|
||||
},
|
||||
start: self => {
|
||||
self.elt.parentNode.classList.add("boxmodel-editing");
|
||||
},
|
||||
change: value => {
|
||||
if (NUMERIC.test(value)) {
|
||||
value += "px";
|
||||
}
|
||||
|
||||
let properties = [
|
||||
{ name: property, value: value }
|
||||
];
|
||||
|
||||
if (property.substring(0, 7) == "border-") {
|
||||
let bprop = property.substring(0, property.length - 5) + "style";
|
||||
let style = session.getProperty(bprop);
|
||||
if (!style || style == "none" || style == "hidden") {
|
||||
properties.push({ name: bprop, value: "solid" });
|
||||
}
|
||||
}
|
||||
|
||||
session.setProperties(properties).catch(e => console.error(e));
|
||||
},
|
||||
done: (value, commit) => {
|
||||
editor.elt.parentNode.classList.remove("boxmodel-editing");
|
||||
if (!commit) {
|
||||
session.revert().then(() => {
|
||||
session.destroy();
|
||||
}, e => console.error(e));
|
||||
return;
|
||||
}
|
||||
|
||||
let node = this.inspector.selection.nodeFront;
|
||||
this.inspector.pageStyle.getLayout(node, {
|
||||
autoMargins: true,
|
||||
}).then(layout => {
|
||||
this.store.dispatch(updateLayout(layout));
|
||||
}, e => console.error(e));
|
||||
},
|
||||
contextMenu: this.inspector.onTextBoxContextMenu,
|
||||
cssProperties: getCssProperties(this.inspector.toolbox)
|
||||
}, event);
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows the box-model highlighter on the currently selected element.
|
||||
*
|
||||
* @param {Object} options
|
||||
* Options passed to the highlighter actor.
|
||||
*/
|
||||
onShowBoxModelHighlighter: (options = {}) => {
|
||||
let toolbox = this.inspector.toolbox;
|
||||
let nodeFront = this.inspector.selection.nodeFront;
|
||||
|
||||
toolbox.highlighterUtils.highlightNodeFront(nodeFront, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for a change in the input checkboxes in the GridList component.
|
||||
|
@ -120,7 +223,6 @@ LayoutView.prototype = {
|
|||
}
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
let provider = createElement(Provider, {
|
||||
|
@ -147,9 +249,16 @@ LayoutView.prototype = {
|
|||
destroy() {
|
||||
this.highlighters.off("grid-highlighter-hidden", this.onHighlighterChange);
|
||||
this.highlighters.off("grid-highlighter-shown", this.onHighlighterChange);
|
||||
this.inspector.selection.off("new-node-front", this.onNewSelection);
|
||||
this.inspector.sidebar.off("select", this.onSidebarSelect);
|
||||
this.layoutInspector.off("grid-layout-changed", this.onGridLayoutChange);
|
||||
|
||||
if (this.reflowFront) {
|
||||
this.untrackReflows();
|
||||
this.reflowFront.destroy();
|
||||
this.reflowFront = null;
|
||||
}
|
||||
|
||||
this.document = null;
|
||||
this.inspector = null;
|
||||
this.layoutInspector = null;
|
||||
|
@ -166,6 +275,16 @@ LayoutView.prototype = {
|
|||
this.inspector.sidebar.getCurrentTabID() === "layoutview";
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if the layout panel is visible and the current node is valid to
|
||||
* be displayed in the view.
|
||||
*/
|
||||
isPanelVisibleAndNodeValid() {
|
||||
return this.isPanelVisible() &&
|
||||
this.inspector.selection.isConnected() &&
|
||||
this.inspector.selection.isElementNode();
|
||||
},
|
||||
|
||||
/**
|
||||
* Load the grid highligher display settings into the store from the stored preferences.
|
||||
*/
|
||||
|
@ -180,13 +299,79 @@ LayoutView.prototype = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Refreshes the layout view by dispatching the new grid data. This is called when the
|
||||
* Starts listening to reflows in the current tab.
|
||||
*/
|
||||
trackReflows() {
|
||||
if (!this.reflowFront) {
|
||||
let { target } = this.inspector;
|
||||
if (target.form.reflowActor) {
|
||||
this.reflowFront = ReflowFront(target.client,
|
||||
target.form);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.reflowFront.on("reflows", this.updateBoxModel);
|
||||
this.reflowFront.start();
|
||||
},
|
||||
|
||||
/**
|
||||
* Stops listening to reflows in the current tab.
|
||||
*/
|
||||
untrackReflows() {
|
||||
if (!this.reflowFront) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.reflowFront.off("reflows", this.updateBoxModel);
|
||||
this.reflowFront.stop();
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the box model panel by dispatching the new layout data.
|
||||
*/
|
||||
updateBoxModel() {
|
||||
let lastRequest = Task.spawn((function* () {
|
||||
if (!(this.isPanelVisible() &&
|
||||
this.inspector.selection.isConnected() &&
|
||||
this.inspector.selection.isElementNode())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let node = this.inspector.selection.nodeFront;
|
||||
let layout = yield this.inspector.pageStyle.getLayout(node, {
|
||||
autoMargins: true,
|
||||
});
|
||||
let styleEntries = yield this.inspector.pageStyle.getApplied(node, {});
|
||||
this.elementRules = styleEntries.map(e => e.rule);
|
||||
|
||||
// Update the redux store with the latest layout properties and update the box
|
||||
// model view.
|
||||
this.store.dispatch(updateLayout(layout));
|
||||
|
||||
// If a subsequent request has been made, wait for that one instead.
|
||||
if (this._lastRequest != lastRequest) {
|
||||
return this._lastRequest;
|
||||
}
|
||||
|
||||
this._lastRequest = null;
|
||||
|
||||
this.inspector.emit("boxmodel-view-updated");
|
||||
return null;
|
||||
}).bind(this)).catch(console.error);
|
||||
|
||||
this._lastRequest = lastRequest;
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the grid panel by dispatching the new grid data. This is called when the
|
||||
* layout view becomes visible or the view needs to be updated with new grid data.
|
||||
*
|
||||
* @param {Array|null} gridFronts
|
||||
* Optional array of all GridFront in the current page.
|
||||
*/
|
||||
refresh: Task.async(function* (gridFronts) {
|
||||
updateGridPanel: Task.async(function* (gridFronts) {
|
||||
// Stop refreshing if the inspector or store is already destroyed.
|
||||
if (!this.inspector || !this.store) {
|
||||
return;
|
||||
|
@ -221,7 +406,7 @@ LayoutView.prototype = {
|
|||
*/
|
||||
onGridLayoutChange(grids) {
|
||||
if (this.isPanelVisible()) {
|
||||
this.refresh(grids);
|
||||
this.updateGridPanel(grids);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -240,6 +425,17 @@ LayoutView.prototype = {
|
|||
this.store.dispatch(updateGridHighlighted(nodeFront, highlighted));
|
||||
},
|
||||
|
||||
/**
|
||||
* Selection 'new-node-front' event handler.
|
||||
*/
|
||||
onNewSelection: function () {
|
||||
if (!this.isPanelVisibleAndNodeValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateBoxModel();
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the inspector sidebar select event. Starts listening for
|
||||
* "grid-layout-changed" if the layout panel is visible. Otherwise, stop
|
||||
|
@ -249,11 +445,18 @@ LayoutView.prototype = {
|
|||
onSidebarSelect() {
|
||||
if (!this.isPanelVisible()) {
|
||||
this.layoutInspector.off("grid-layout-changed", this.onGridLayoutChange);
|
||||
this.untrackReflows();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.inspector.selection.isConnected() &&
|
||||
this.inspector.selection.isElementNode()) {
|
||||
this.trackReflows();
|
||||
}
|
||||
|
||||
this.layoutInspector.on("grid-layout-changed", this.onGridLayoutChange);
|
||||
this.refresh();
|
||||
this.updateBoxModel();
|
||||
this.updateGridPanel();
|
||||
},
|
||||
|
||||
};
|
||||
|
|
|
@ -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 {
|
||||
UPDATE_LAYOUT,
|
||||
} = require("../actions/index");
|
||||
|
||||
const INITIAL_BOX_MODEL = {
|
||||
layout: {},
|
||||
};
|
||||
|
||||
let reducers = {
|
||||
|
||||
[UPDATE_LAYOUT](boxModel, { layout }) {
|
||||
return Object.assign({}, boxModel, {
|
||||
layout,
|
||||
});
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
module.exports = function (boxModel = INITIAL_BOX_MODEL, action) {
|
||||
let reducer = reducers[action.type];
|
||||
if (!reducer) {
|
||||
return boxModel;
|
||||
}
|
||||
return reducer(boxModel, action);
|
||||
};
|
|
@ -4,5 +4,6 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
exports.boxModel = require("./box-model");
|
||||
exports.grids = require("./grids");
|
||||
exports.highlighterSettings = require("./highlighter-settings");
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
'box-model.js',
|
||||
'grids.js',
|
||||
'highlighter-settings.js',
|
||||
'index.js',
|
||||
|
|
|
@ -6,6 +6,16 @@
|
|||
|
||||
const { PropTypes } = require("devtools/client/shared/vendor/react");
|
||||
|
||||
/**
|
||||
* The box model data for the current selected node.
|
||||
*/
|
||||
exports.boxModel = {
|
||||
|
||||
// The layout information of the current selected node
|
||||
layout: PropTypes.object,
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* A single grid container in the document.
|
||||
*/
|
||||
|
|
|
@ -2,13 +2,11 @@
|
|||
* 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/ */
|
||||
|
||||
#boxmodel-wrapper {
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
border-color: var(--theme-splitter-color);
|
||||
}
|
||||
/**
|
||||
* This is the stylesheet of the Box Model view implemented in the layout panel.
|
||||
*/
|
||||
|
||||
#boxmodel-container {
|
||||
.boxmodel-container {
|
||||
/* The view will grow bigger as the window gets resized, until 400px */
|
||||
max-width: 400px;
|
||||
margin: 0px auto;
|
||||
|
@ -17,24 +15,24 @@
|
|||
|
||||
/* Header */
|
||||
|
||||
#boxmodel-header,
|
||||
#boxmodel-info {
|
||||
.boxmodel-header,
|
||||
.boxmodel-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px 17px;
|
||||
}
|
||||
|
||||
#layout-geometry-editor {
|
||||
.layout-geometry-editor {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#layout-geometry-editor::before {
|
||||
.layout-geometry-editor::before {
|
||||
background: url(images/geometry-editor.svg) no-repeat center center / 16px 16px;
|
||||
}
|
||||
|
||||
/* Main: contains the box-model regions */
|
||||
|
||||
#boxmodel-main {
|
||||
.boxmodel-main {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
/* The regions are semi-transparent, so the white background is partly
|
||||
|
@ -42,7 +40,7 @@
|
|||
background-color: white;
|
||||
color: var(--theme-selection-color);
|
||||
/* Make sure there is some space between the window's edges and the regions */
|
||||
margin: 0 14px 4px 14px;
|
||||
margin: 14px 14px 4px 14px;
|
||||
width: calc(100% - 2 * 14px);
|
||||
}
|
||||
|
||||
|
@ -53,73 +51,73 @@
|
|||
|
||||
/* Regions are 3 nested elements with wide borders and outlines */
|
||||
|
||||
#boxmodel-content {
|
||||
.boxmodel-content {
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
#boxmodel-margins,
|
||||
#boxmodel-borders,
|
||||
#boxmodel-padding {
|
||||
.boxmodel-margins,
|
||||
.boxmodel-borders,
|
||||
.boxmodel-paddings {
|
||||
border-color: hsla(210,100%,85%,0.2);
|
||||
border-width: 18px;
|
||||
border-style: solid;
|
||||
outline: dotted 1px hsl(210,100%,85%);
|
||||
}
|
||||
|
||||
#boxmodel-margins {
|
||||
.boxmodel-margins {
|
||||
/* This opacity applies to all of the regions, since they are nested */
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
/* Regions colors */
|
||||
|
||||
#boxmodel-margins {
|
||||
.boxmodel-margins {
|
||||
border-color: #edff64;
|
||||
}
|
||||
|
||||
#boxmodel-borders {
|
||||
.boxmodel-borders {
|
||||
border-color: #444444;
|
||||
}
|
||||
|
||||
#boxmodel-padding {
|
||||
.boxmodel-paddings {
|
||||
border-color: #6a5acd;
|
||||
}
|
||||
|
||||
#boxmodel-content {
|
||||
.boxmodel-content {
|
||||
background-color: #87ceeb;
|
||||
}
|
||||
|
||||
.theme-firebug #boxmodel-main,
|
||||
.theme-firebug #boxmodel-borders,
|
||||
.theme-firebug #boxmodel-content {
|
||||
.theme-firebug .boxmodel-main,
|
||||
.theme-firebug .boxmodel-borders,
|
||||
.theme-firebug .boxmodel-content {
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.theme-firebug #boxmodel-main,
|
||||
.theme-firebug #boxmodel-header {
|
||||
.theme-firebug .boxmodel-main,
|
||||
.theme-firebug .boxmodel-header {
|
||||
font-family: var(--proportional-font-family);
|
||||
}
|
||||
|
||||
.theme-firebug #boxmodel-main {
|
||||
.theme-firebug .boxmodel-main {
|
||||
color: var(--theme-body-color);
|
||||
font-size: var(--theme-toolbar-font-size);
|
||||
}
|
||||
|
||||
.theme-firebug #boxmodel-header {
|
||||
.theme-firebug .boxmodel-header {
|
||||
font-size: var(--theme-toolbar-font-size);
|
||||
}
|
||||
|
||||
/* Editable region sizes are contained in absolutely positioned <p> */
|
||||
|
||||
#boxmodel-main > p {
|
||||
.boxmodel-main > p {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#boxmodel-main > p > span,
|
||||
#boxmodel-main > p > input {
|
||||
.boxmodel-main > p > span,
|
||||
.boxmodel-main > p > input {
|
||||
vertical-align: middle;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
@ -235,11 +233,6 @@
|
|||
border-bottom-color: hsl(0, 0%, 50%);
|
||||
}
|
||||
|
||||
.styleinspector-propertyeditor {
|
||||
border: 1px solid #ccc;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Make sure the content size doesn't appear as editable like the other sizes */
|
||||
|
||||
.boxmodel-size > span {
|
||||
|
@ -248,11 +241,11 @@
|
|||
|
||||
/* Box Model Info: contains the position and size of the element */
|
||||
|
||||
#boxmodel-element-size {
|
||||
.boxmodel-element-size {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#boxmodel-position-group {
|
||||
.boxmodel-position-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче