diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /devtools/client/inspector/changes/components/ChangesApp.js | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/inspector/changes/components/ChangesApp.js')
-rw-r--r-- | devtools/client/inspector/changes/components/ChangesApp.js | 241 |
1 files changed, 241 insertions, 0 deletions
diff --git a/devtools/client/inspector/changes/components/ChangesApp.js b/devtools/client/inspector/changes/components/ChangesApp.js new file mode 100644 index 0000000000..9953109e19 --- /dev/null +++ b/devtools/client/inspector/changes/components/ChangesApp.js @@ -0,0 +1,241 @@ +/* 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 { + connect, +} = require("resource://devtools/client/shared/vendor/react-redux.js"); + +const CSSDeclaration = createFactory( + require("resource://devtools/client/inspector/changes/components/CSSDeclaration.js") +); +const { + getChangesTree, +} = require("resource://devtools/client/inspector/changes/selectors/changes.js"); +const { + getSourceForDisplay, +} = require("resource://devtools/client/inspector/changes/utils/changes-utils.js"); +const { + getStr, +} = require("resource://devtools/client/inspector/changes/utils/l10n.js"); + +class ChangesApp extends PureComponent { + static get propTypes() { + return { + // Nested CSS rule tree structure of CSS changes grouped by source (stylesheet) + changesTree: PropTypes.object.isRequired, + // Event handler for "contextmenu" event + onContextMenu: PropTypes.func.isRequired, + // Event handler for click on "Copy All Changes" button + onCopyAllChanges: PropTypes.func.isRequired, + // Event handler for click on "Copy Rule" button + onCopyRule: PropTypes.func.isRequired, + }; + } + + constructor(props) { + super(props); + } + + renderCopyAllChangesButton() { + const button = dom.button( + { + className: "changes__copy-all-changes-button", + onClick: e => { + e.stopPropagation(); + this.props.onCopyAllChanges(); + }, + title: getStr("changes.contextmenu.copyAllChangesDescription"), + }, + getStr("changes.contextmenu.copyAllChanges") + ); + + return dom.div({ className: "changes__toolbar" }, button); + } + + renderCopyButton(ruleId) { + return dom.button( + { + className: "changes__copy-rule-button", + onClick: e => { + e.stopPropagation(); + this.props.onCopyRule(ruleId); + }, + title: getStr("changes.contextmenu.copyRuleDescription"), + }, + getStr("changes.contextmenu.copyRule") + ); + } + + renderDeclarations(remove = [], add = []) { + const removals = remove + // Sorting changed declarations in the order they appear in the Rules view. + .sort((a, b) => a.index > b.index) + .map(({ property, value, index }) => { + return CSSDeclaration({ + key: "remove-" + property + index, + className: "level diff-remove", + property, + value, + }); + }); + + const additions = add + // Sorting changed declarations in the order they appear in the Rules view. + .sort((a, b) => a.index > b.index) + .map(({ property, value, index }) => { + return CSSDeclaration({ + key: "add-" + property + index, + className: "level diff-add", + property, + value, + }); + }); + + return [removals, additions]; + } + + renderRule(ruleId, rule, level = 0) { + const diffClass = rule.isNew ? "diff-add" : ""; + return dom.div( + { + key: ruleId, + className: "changes__rule devtools-monospace", + "data-rule-id": ruleId, + style: { + "--diff-level": level, + }, + }, + this.renderSelectors(rule.selectors, rule.isNew), + this.renderCopyButton(ruleId), + // Render any nested child rules if they exist. + rule.children.map(childRule => { + return this.renderRule(childRule.ruleId, childRule, level + 1); + }), + // Render any changed CSS declarations. + this.renderDeclarations(rule.remove, rule.add), + // Render the closing bracket with a diff marker if necessary. + dom.div({ className: `level ${diffClass}` }, "}") + ); + } + + /** + * Return an array of React elements for the rule's selector. + * + * @param {Array} selectors + * List of strings as versions of this rule's selector over time. + * @param {Boolean} isNewRule + * Whether the rule was created at runtime. + * @return {Array} + */ + renderSelectors(selectors, isNewRule) { + const selectorDiffClassMap = new Map(); + + // The selectors array has just one item if it hasn't changed. Render it as-is. + // If the rule was created at runtime, mark the single selector as added. + // If it has two or more items, the first item was the original selector (mark as + // removed) and the last item is the current selector (mark as added). + if (selectors.length === 1) { + selectorDiffClassMap.set(selectors[0], isNewRule ? "diff-add" : ""); + } else if (selectors.length >= 2) { + selectorDiffClassMap.set(selectors[0], "diff-remove"); + selectorDiffClassMap.set(selectors[selectors.length - 1], "diff-add"); + } + + const elements = []; + + for (const [selector, diffClass] of selectorDiffClassMap) { + elements.push( + dom.div( + { + key: selector, + className: `level changes__selector ${diffClass}`, + title: selector, + }, + selector, + dom.span({}, " {") + ) + ); + } + + return elements; + } + + renderDiff(changes = {}) { + // Render groups of style sources: stylesheets and element style attributes. + return Object.entries(changes).map(([sourceId, source]) => { + const path = getSourceForDisplay(source); + const { href, rules, isFramed } = source; + + return dom.div( + { + key: sourceId, + "data-source-id": sourceId, + className: "source", + }, + dom.div( + { + className: "href", + title: href, + }, + dom.span({}, path), + isFramed && this.renderFrameBadge(href) + ), + // Render changed rules within this source. + Object.entries(rules).map(([ruleId, rule]) => { + return this.renderRule(ruleId, rule); + }) + ); + }); + } + + renderFrameBadge(href = "") { + return dom.span( + { + className: "inspector-badge", + title: href, + }, + getStr("changes.iframeLabel") + ); + } + + renderEmptyState() { + return dom.div( + { className: "devtools-sidepanel-no-result" }, + dom.p({}, getStr("changes.noChanges")), + dom.p({}, getStr("changes.noChangesDescription")) + ); + } + + render() { + const hasChanges = !!Object.keys(this.props.changesTree).length; + return dom.div( + { + className: "theme-sidebar inspector-tabpanel", + id: "sidebar-panel-changes", + role: "document", + tabIndex: "0", + onContextMenu: this.props.onContextMenu, + }, + !hasChanges && this.renderEmptyState(), + hasChanges && this.renderCopyAllChangesButton(), + hasChanges && this.renderDiff(this.props.changesTree) + ); + } +} + +const mapStateToProps = state => { + return { + changesTree: getChangesTree(state.changes), + }; +}; + +module.exports = connect(mapStateToProps)(ChangesApp); |