diff options
Diffstat (limited to 'devtools/client/inspector/compatibility/components')
13 files changed, 1150 insertions, 0 deletions
diff --git a/devtools/client/inspector/compatibility/components/BrowserIcon.js b/devtools/client/inspector/compatibility/components/BrowserIcon.js new file mode 100644 index 0000000000..f452ba608a --- /dev/null +++ b/devtools/client/inspector/compatibility/components/BrowserIcon.js @@ -0,0 +1,82 @@ +/* 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("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 Types = require("resource://devtools/client/inspector/compatibility/types.js"); + +const ICONS = { + firefox: { + src: "chrome://devtools/skin/images/browsers/firefox.svg", + isMobileIconNeeded: false, + }, + firefox_android: { + src: "chrome://devtools/skin/images/browsers/firefox.svg", + isMobileIconNeeded: true, + }, + chrome: { + src: "chrome://devtools/skin/images/browsers/chrome.svg", + isMobileIconNeeded: false, + }, + chrome_android: { + src: "chrome://devtools/skin/images/browsers/chrome.svg", + isMobileIconNeeded: true, + }, + safari: { + src: "chrome://devtools/skin/images/browsers/safari.svg", + isMobileIconNeeded: false, + }, + safari_ios: { + src: "chrome://devtools/skin/images/browsers/safari.svg", + isMobileIconNeeded: true, + }, + edge: { + src: "chrome://devtools/skin/images/browsers/edge.svg", + isMobileIconNeeded: false, + }, + ie: { + src: "chrome://devtools/skin/images/browsers/ie.svg", + isMobileIconNeeded: false, + }, +}; + +class BrowserIcon extends PureComponent { + static get propTypes() { + return { + id: Types.browser.id, + title: PropTypes.string, + name: PropTypes.string, + }; + } + + render() { + const { id, name, title } = this.props; + + const icon = ICONS[id]; + + return dom.span( + { + className: + "compatibility-browser-icon" + + (icon.isMobileIconNeeded + ? " compatibility-browser-icon--mobile" + : ""), + }, + dom.img({ + className: "compatibility-browser-icon__image", + alt: name || title, + title, + src: icon.src, + }) + ); + } +} + +module.exports = BrowserIcon; diff --git a/devtools/client/inspector/compatibility/components/CompatibilityApp.js b/devtools/client/inspector/compatibility/components/CompatibilityApp.js new file mode 100644 index 0000000000..6091263d42 --- /dev/null +++ b/devtools/client/inspector/compatibility/components/CompatibilityApp.js @@ -0,0 +1,126 @@ +/* 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 { + connect, +} = require("resource://devtools/client/shared/vendor/react-redux.js"); +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 FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js"); + +const Types = require("resource://devtools/client/inspector/compatibility/types.js"); + +const Accordion = createFactory( + require("resource://devtools/client/shared/components/Accordion.js") +); +const Footer = createFactory( + require("resource://devtools/client/inspector/compatibility/components/Footer.js") +); +const IssuePane = createFactory( + require("resource://devtools/client/inspector/compatibility/components/IssuePane.js") +); +const Settings = createFactory( + require("resource://devtools/client/inspector/compatibility/components/Settings.js") +); + +class CompatibilityApp extends PureComponent { + static get propTypes() { + return { + dispatch: PropTypes.func.isRequired, + // getString prop is injected by the withLocalization wrapper + getString: PropTypes.func.isRequired, + isSettingsVisibile: PropTypes.bool.isRequired, + isTopLevelTargetProcessing: PropTypes.bool.isRequired, + selectedNodeIssues: PropTypes.arrayOf(PropTypes.shape(Types.issue)) + .isRequired, + topLevelTargetIssues: PropTypes.arrayOf(PropTypes.shape(Types.issue)) + .isRequired, + setSelectedNode: PropTypes.func.isRequired, + }; + } + + render() { + const { + dispatch, + getString, + isSettingsVisibile, + isTopLevelTargetProcessing, + selectedNodeIssues, + topLevelTargetIssues, + setSelectedNode, + } = this.props; + + const selectedNodeIssuePane = IssuePane({ + issues: selectedNodeIssues, + }); + + const topLevelTargetIssuePane = + topLevelTargetIssues.length || !isTopLevelTargetProcessing + ? IssuePane({ + dispatch, + issues: topLevelTargetIssues, + setSelectedNode, + }) + : null; + + const throbber = isTopLevelTargetProcessing + ? dom.div({ + className: "compatibility-app__throbber devtools-throbber", + }) + : null; + + return dom.section( + { + className: "compatibility-app theme-sidebar inspector-tabpanel", + }, + dom.div( + { + className: + "compatibility-app__container" + + (isSettingsVisibile ? " compatibility-app__container-hidden" : ""), + }, + Accordion({ + className: "compatibility-app__main", + items: [ + { + id: "compatibility-app--selected-element-pane", + header: getString("compatibility-selected-element-header"), + component: selectedNodeIssuePane, + opened: true, + }, + { + id: "compatibility-app--all-elements-pane", + header: getString("compatibility-all-elements-header"), + component: [topLevelTargetIssuePane, throbber], + opened: true, + }, + ], + }), + Footer({ + className: "compatibility-app__footer", + }) + ), + isSettingsVisibile ? Settings() : null + ); + } +} + +const mapStateToProps = state => { + return { + isSettingsVisibile: state.compatibility.isSettingsVisibile, + isTopLevelTargetProcessing: state.compatibility.isTopLevelTargetProcessing, + selectedNodeIssues: state.compatibility.selectedNodeIssues, + topLevelTargetIssues: state.compatibility.topLevelTargetIssues, + }; +}; +module.exports = FluentReact.withLocalization( + connect(mapStateToProps)(CompatibilityApp) +); diff --git a/devtools/client/inspector/compatibility/components/Footer.js b/devtools/client/inspector/compatibility/components/Footer.js new file mode 100644 index 0000000000..c48484b1c4 --- /dev/null +++ b/devtools/client/inspector/compatibility/components/Footer.js @@ -0,0 +1,85 @@ +/* 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 { + connect, +} = require("resource://devtools/client/shared/vendor/react-redux.js"); +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 FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js"); +const Localized = createFactory(FluentReact.Localized); + +const { + updateSettingsVisibility, +} = require("resource://devtools/client/inspector/compatibility/actions/compatibility.js"); + +const SETTINGS_ICON = "chrome://devtools/skin/images/settings.svg"; + +class Footer extends PureComponent { + static get propTypes() { + return { + updateSettingsVisibility: PropTypes.func.isRequired, + }; + } + + _renderButton(icon, labelId, titleId, onClick) { + return Localized( + { + id: titleId, + attrs: { title: true }, + }, + dom.button( + { + className: "compatibility-footer__button", + title: titleId, + onClick, + }, + dom.img({ + className: "compatibility-footer__icon", + src: icon, + }), + Localized( + { + id: labelId, + }, + dom.label( + { + className: "compatibility-footer__label", + }, + labelId + ) + ) + ) + ); + } + + render() { + return dom.footer( + { + className: "compatibility-footer", + }, + this._renderButton( + SETTINGS_ICON, + "compatibility-settings-button-label", + "compatibility-settings-button-title", + this.props.updateSettingsVisibility + ) + ); + } +} + +const mapDispatchToProps = dispatch => { + return { + updateSettingsVisibility: () => dispatch(updateSettingsVisibility(true)), + }; +}; + +module.exports = connect(null, mapDispatchToProps)(Footer); diff --git a/devtools/client/inspector/compatibility/components/IssueItem.js b/devtools/client/inspector/compatibility/components/IssueItem.js new file mode 100644 index 0000000000..c576e58223 --- /dev/null +++ b/devtools/client/inspector/compatibility/components/IssueItem.js @@ -0,0 +1,245 @@ +/* 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 FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js"); +const Localized = createFactory(FluentReact.Localized); + +loader.lazyRequireGetter( + this, + "openDocLink", + "resource://devtools/client/shared/link.js", + true +); + +const UnsupportedBrowserList = createFactory( + require("resource://devtools/client/inspector/compatibility/components/UnsupportedBrowserList.js") +); + +const Types = require("resource://devtools/client/inspector/compatibility/types.js"); + +const NodePane = createFactory( + require("resource://devtools/client/inspector/compatibility/components/NodePane.js") +); + +// For test +loader.lazyRequireGetter( + this, + "toSnakeCase", + "resource://devtools/client/inspector/compatibility/utils/cases.js", + true +); + +const MDN_LINK_PARAMS = new URLSearchParams({ + utm_source: "devtools", + utm_medium: "inspector-compatibility", + utm_campaign: "default", +}); + +class IssueItem extends PureComponent { + static get propTypes() { + return { + ...Types.issue, + dispatch: PropTypes.func.isRequired, + setSelectedNode: PropTypes.func.isRequired, + }; + } + + constructor(props) { + super(props); + this._onLinkClicked = this._onLinkClicked.bind(this); + } + + _onLinkClicked(e) { + e.preventDefault(); + e.stopPropagation(); + + const isMacOS = Services.appinfo.OS === "Darwin"; + + openDocLink(e.target.href, { + relatedToCurrent: true, + inBackground: isMacOS ? e.metaKey : e.ctrlKey, + }); + } + + _getTestDataAttributes() { + const testDataSet = {}; + + if (Services.prefs.getBoolPref("devtools.testing", false)) { + for (const [key, value] of Object.entries(this.props)) { + if (key === "nodes") { + continue; + } + const datasetKey = `data-qa-${toSnakeCase(key)}`; + testDataSet[datasetKey] = JSON.stringify(value); + } + } + + return testDataSet; + } + + _renderAliases() { + const { property } = this.props; + let { aliases } = this.props; + + if (!aliases) { + return null; + } + + aliases = aliases.filter(alias => alias !== property); + + if (aliases.length === 0) { + return null; + } + + return dom.ul( + { + className: "compatibility-issue-item__aliases", + }, + aliases.map(alias => + dom.li( + { + key: alias, + className: "compatibility-issue-item__alias", + }, + alias + ) + ) + ); + } + + _renderCauses() { + const { deprecated, experimental, prefixNeeded } = this.props; + + if (!deprecated && !experimental && !prefixNeeded) { + return null; + } + + let localizationId = ""; + + if (deprecated && experimental && prefixNeeded) { + localizationId = + "compatibility-issue-deprecated-experimental-prefixneeded"; + } else if (deprecated && experimental) { + localizationId = "compatibility-issue-deprecated-experimental"; + } else if (deprecated && prefixNeeded) { + localizationId = "compatibility-issue-deprecated-prefixneeded"; + } else if (experimental && prefixNeeded) { + localizationId = "compatibility-issue-experimental-prefixneeded"; + } else if (deprecated) { + localizationId = "compatibility-issue-deprecated"; + } else if (experimental) { + localizationId = "compatibility-issue-experimental"; + } else if (prefixNeeded) { + localizationId = "compatibility-issue-prefixneeded"; + } + + return Localized( + { + id: localizationId, + }, + dom.span( + { className: "compatibility-issue-item__causes" }, + localizationId + ) + ); + } + + _renderPropertyEl() { + const { property, url, specUrl } = this.props; + const baseCls = "compatibility-issue-item__property devtools-monospace"; + if (!url && !specUrl) { + return dom.span({ className: baseCls }, property); + } + + const href = url ? `${url}?${MDN_LINK_PARAMS}` : specUrl; + + return dom.a( + { + className: `${baseCls} ${ + url + ? "compatibility-issue-item__mdn-link" + : "compatibility-issue-item__spec-link" + }`, + href, + title: href, + onClick: e => this._onLinkClicked(e), + }, + property + ); + } + + _renderDescription() { + return dom.div( + { + className: "compatibility-issue-item__description", + }, + this._renderPropertyEl(), + this._renderCauses(), + this._renderUnsupportedBrowserList() + ); + } + + _renderNodeList() { + const { dispatch, nodes, setSelectedNode } = this.props; + + if (!nodes) { + return null; + } + + return NodePane({ + dispatch, + nodes, + setSelectedNode, + }); + } + + _renderUnsupportedBrowserList() { + const { unsupportedBrowsers } = this.props; + + return unsupportedBrowsers.length + ? UnsupportedBrowserList({ browsers: unsupportedBrowsers }) + : null; + } + + render() { + const { deprecated, experimental, property, unsupportedBrowsers } = + this.props; + + const classes = ["compatibility-issue-item"]; + + if (deprecated) { + classes.push("compatibility-issue-item--deprecated"); + } + + if (experimental) { + classes.push("compatibility-issue-item--experimental"); + } + + if (unsupportedBrowsers.length) { + classes.push("compatibility-issue-item--unsupported"); + } + + return dom.li( + { + className: classes.join(" "), + key: property, + ...this._getTestDataAttributes(), + }, + this._renderDescription(), + this._renderAliases(), + this._renderNodeList() + ); + } +} + +module.exports = IssueItem; diff --git a/devtools/client/inspector/compatibility/components/IssueList.js b/devtools/client/inspector/compatibility/components/IssueList.js new file mode 100644 index 0000000000..f334276cb3 --- /dev/null +++ b/devtools/client/inspector/compatibility/components/IssueList.js @@ -0,0 +1,45 @@ +/* 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 Types = require("resource://devtools/client/inspector/compatibility/types.js"); + +const IssueItem = createFactory( + require("resource://devtools/client/inspector/compatibility/components/IssueItem.js") +); + +class IssueList extends PureComponent { + static get propTypes() { + return { + dispatch: PropTypes.func.isRequired, + issues: PropTypes.arrayOf(PropTypes.shape(Types.issue)).isRequired, + setSelectedNode: PropTypes.func.isRequired, + }; + } + + render() { + const { dispatch, issues, setSelectedNode } = this.props; + + return dom.ul( + { className: "compatibility-issue-list" }, + issues.map(issue => + IssueItem({ + ...issue, + dispatch, + setSelectedNode, + }) + ) + ); + } +} + +module.exports = IssueList; diff --git a/devtools/client/inspector/compatibility/components/IssuePane.js b/devtools/client/inspector/compatibility/components/IssuePane.js new file mode 100644 index 0000000000..b313274d9a --- /dev/null +++ b/devtools/client/inspector/compatibility/components/IssuePane.js @@ -0,0 +1,55 @@ +/* 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 FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js"); +const Localized = createFactory(FluentReact.Localized); + +const Types = require("resource://devtools/client/inspector/compatibility/types.js"); + +const IssueList = createFactory( + require("resource://devtools/client/inspector/compatibility/components/IssueList.js") +); + +class IssuePane extends PureComponent { + static get propTypes() { + return { + dispatch: PropTypes.func.isRequired, + issues: PropTypes.arrayOf(PropTypes.shape(Types.issue)).isRequired, + setSelectedNode: PropTypes.func.isRequired, + }; + } + + _renderNoIssues() { + return Localized( + { id: "compatibility-no-issues-found" }, + dom.p( + { className: "devtools-sidepanel-no-result" }, + "compatibility-no-issues-found" + ) + ); + } + + render() { + const { dispatch, issues, setSelectedNode } = this.props; + + return issues.length + ? IssueList({ + dispatch, + issues, + setSelectedNode, + }) + : this._renderNoIssues(); + } +} + +module.exports = IssuePane; diff --git a/devtools/client/inspector/compatibility/components/NodeItem.js b/devtools/client/inspector/compatibility/components/NodeItem.js new file mode 100644 index 0000000000..078b04a3b4 --- /dev/null +++ b/devtools/client/inspector/compatibility/components/NodeItem.js @@ -0,0 +1,59 @@ +/* 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("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 { + translateNodeFrontToGrip, +} = require("resource://devtools/client/inspector/shared/utils.js"); +const { + REPS, + MODE, +} = require("resource://devtools/client/shared/components/reps/index.js"); +const { Rep } = REPS; +const ElementNode = REPS.ElementNode; + +const Types = require("resource://devtools/client/inspector/compatibility/types.js"); + +const { + highlightNode, + unhighlightNode, +} = require("resource://devtools/client/inspector/boxmodel/actions/box-model-highlighter.js"); + +class NodeItem extends PureComponent { + static get propTypes() { + return { + dispatch: PropTypes.func.isRequired, + node: Types.node.isRequired, + setSelectedNode: PropTypes.func.isRequired, + }; + } + + render() { + const { dispatch, node, setSelectedNode } = this.props; + + return dom.li( + { className: "compatibility-node-item" }, + Rep({ + defaultRep: ElementNode, + mode: MODE.TINY, + object: translateNodeFrontToGrip(node), + onDOMNodeClick: () => { + setSelectedNode(node); + dispatch(unhighlightNode()); + }, + onDOMNodeMouseOut: () => dispatch(unhighlightNode()), + onDOMNodeMouseOver: () => dispatch(highlightNode(node)), + }) + ); + } +} + +module.exports = NodeItem; diff --git a/devtools/client/inspector/compatibility/components/NodeList.js b/devtools/client/inspector/compatibility/components/NodeList.js new file mode 100644 index 0000000000..7dc93c8b06 --- /dev/null +++ b/devtools/client/inspector/compatibility/components/NodeList.js @@ -0,0 +1,45 @@ +/* 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 Types = require("resource://devtools/client/inspector/compatibility/types.js"); + +const NodeItem = createFactory( + require("resource://devtools/client/inspector/compatibility/components/NodeItem.js") +); + +class NodeList extends PureComponent { + static get propTypes() { + return { + dispatch: PropTypes.func.isRequired, + nodes: PropTypes.arrayOf(Types.node).isRequired, + setSelectedNode: PropTypes.func.isRequired, + }; + } + + render() { + const { dispatch, nodes, setSelectedNode } = this.props; + + return dom.ul( + { className: "compatibility-node-list" }, + nodes.map(node => + NodeItem({ + dispatch, + node, + setSelectedNode, + }) + ) + ); + } +} + +module.exports = NodeList; diff --git a/devtools/client/inspector/compatibility/components/NodePane.js b/devtools/client/inspector/compatibility/components/NodePane.js new file mode 100644 index 0000000000..06c844d012 --- /dev/null +++ b/devtools/client/inspector/compatibility/components/NodePane.js @@ -0,0 +1,55 @@ +/* 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 FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js"); +const Localized = createFactory(FluentReact.Localized); + +const Types = require("resource://devtools/client/inspector/compatibility/types.js"); + +const NodeList = createFactory( + require("resource://devtools/client/inspector/compatibility/components/NodeList.js") +); + +class NodePane extends PureComponent { + static get propTypes() { + return { + dispatch: PropTypes.func.isRequired, + nodes: PropTypes.arrayOf(Types.node).isRequired, + setSelectedNode: PropTypes.func.isRequired, + }; + } + + render() { + const { nodes } = this.props; + + return dom.details( + { + className: "compatibility-node-pane", + open: nodes.length <= 1, + }, + Localized( + { + id: "compatibility-issue-occurrences", + $number: nodes.length, + }, + dom.summary( + { className: "compatibility-node-pane__summary" }, + "compatibility-issue-occurrences" + ) + ), + NodeList(this.props) + ); + } +} + +module.exports = NodePane; diff --git a/devtools/client/inspector/compatibility/components/Settings.js b/devtools/client/inspector/compatibility/components/Settings.js new file mode 100644 index 0000000000..6f55353aa6 --- /dev/null +++ b/devtools/client/inspector/compatibility/components/Settings.js @@ -0,0 +1,197 @@ +/* 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 { + connect, +} = require("resource://devtools/client/shared/vendor/react-redux.js"); +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 FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js"); +const Localized = createFactory(FluentReact.Localized); + +const Types = require("resource://devtools/client/inspector/compatibility/types.js"); + +const BrowserIcon = createFactory( + require("resource://devtools/client/inspector/compatibility/components/BrowserIcon.js") +); + +const { + updateSettingsVisibility, + updateTargetBrowsers, +} = require("resource://devtools/client/inspector/compatibility/actions/compatibility.js"); + +const CLOSE_ICON = "chrome://devtools/skin/images/close.svg"; + +class Settings extends PureComponent { + static get propTypes() { + return { + defaultTargetBrowsers: PropTypes.arrayOf(PropTypes.shape(Types.browser)) + .isRequired, + targetBrowsers: PropTypes.arrayOf(PropTypes.shape(Types.browser)) + .isRequired, + updateTargetBrowsers: PropTypes.func.isRequired, + updateSettingsVisibility: PropTypes.func.isRequired, + }; + } + + constructor(props) { + super(props); + + this._onTargetBrowserChanged = this._onTargetBrowserChanged.bind(this); + + this.state = { + targetBrowsers: props.targetBrowsers, + }; + } + + _onTargetBrowserChanged({ target }) { + const { id, status } = target.dataset; + let { targetBrowsers } = this.state; + + if (target.checked) { + targetBrowsers = [...targetBrowsers, { id, status }]; + } else { + targetBrowsers = targetBrowsers.filter( + b => !(b.id === id && b.status === status) + ); + } + + this.setState({ targetBrowsers }); + } + + _renderTargetBrowsers() { + const { defaultTargetBrowsers } = this.props; + const { targetBrowsers } = this.state; + + return dom.section( + { + className: "compatibility-settings__target-browsers", + }, + Localized( + { id: "compatibility-target-browsers-header" }, + dom.header( + { + className: "compatibility-settings__target-browsers-header", + }, + "compatibility-target-browsers-header" + ) + ), + dom.ul( + { + className: "compatibility-settings__target-browsers-list", + }, + defaultTargetBrowsers.map(({ id, name, status, version }) => { + const inputId = `${id}-${status}`; + const isTargetBrowser = !!targetBrowsers.find( + b => b.id === id && b.status === status + ); + return dom.li( + { + className: "compatibility-settings__target-browsers-item", + }, + dom.input({ + id: inputId, + type: "checkbox", + checked: isTargetBrowser, + onChange: this._onTargetBrowserChanged, + "data-id": id, + "data-status": status, + }), + dom.label( + { + className: "compatibility-settings__target-browsers-item-label", + htmlFor: inputId, + }, + BrowserIcon({ id, title: `${name} ${status}` }), + `${name} ${status} (${version})` + ) + ); + }) + ) + ); + } + + _renderHeader() { + return dom.header( + { + className: "compatibility-settings__header", + }, + Localized( + { id: "compatibility-settings-header" }, + dom.label( + { + className: "compatibility-settings__header-label", + }, + "compatibility-settings-header" + ) + ), + Localized( + { + id: "compatibility-close-settings-button", + attrs: { title: true }, + }, + dom.button( + { + className: "compatibility-settings__header-button", + title: "compatibility-close-settings-button", + onClick: () => { + const { defaultTargetBrowsers } = this.props; + const { targetBrowsers } = this.state; + + // Sort by ordering of default browsers. + const browsers = defaultTargetBrowsers.filter(b => + targetBrowsers.find(t => t.id === b.id && t.status === b.status) + ); + + if ( + this.props.targetBrowsers.toString() !== browsers.toString() + ) { + this.props.updateTargetBrowsers(browsers); + } + + this.props.updateSettingsVisibility(); + }, + }, + dom.img({ + className: "compatibility-settings__header-icon", + src: CLOSE_ICON, + }) + ) + ) + ); + } + + render() { + return dom.section( + { + className: "compatibility-settings", + }, + this._renderHeader(), + this._renderTargetBrowsers() + ); + } +} + +const mapStateToProps = state => { + return { + defaultTargetBrowsers: state.compatibility.defaultTargetBrowsers, + targetBrowsers: state.compatibility.targetBrowsers, + }; +}; + +const mapDispatchToProps = dispatch => { + return { + updateTargetBrowsers: browsers => dispatch(updateTargetBrowsers(browsers)), + updateSettingsVisibility: () => dispatch(updateSettingsVisibility(false)), + }; +}; + +module.exports = connect(mapStateToProps, mapDispatchToProps)(Settings); diff --git a/devtools/client/inspector/compatibility/components/UnsupportedBrowserItem.js b/devtools/client/inspector/compatibility/components/UnsupportedBrowserItem.js new file mode 100644 index 0000000000..ae9806d206 --- /dev/null +++ b/devtools/client/inspector/compatibility/components/UnsupportedBrowserItem.js @@ -0,0 +1,60 @@ +/* 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 BrowserIcon = createFactory( + require("resource://devtools/client/inspector/compatibility/components/BrowserIcon.js") +); +const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js"); +const Localized = createFactory(FluentReact.Localized); + +const Types = require("resource://devtools/client/inspector/compatibility/types.js"); + +class UnsupportedBrowserItem extends PureComponent { + static get propTypes() { + return { + id: Types.browser.id, + name: Types.browser.name, + unsupportedVersions: PropTypes.array.isRequired, + version: Types.browser.version, + }; + } + + render() { + const { unsupportedVersions, id, name, version } = this.props; + + return Localized( + { + id: "compatibility-issue-browsers-list", + $browsers: unsupportedVersions + .map( + ({ version: v, status }) => + `${name} ${v}${status ? ` (${status})` : ""}` + ) + .join("\n"), + attrs: { title: true }, + }, + dom.li( + { className: "compatibility-browser", "data-browser-id": id }, + BrowserIcon({ id, name }), + dom.span( + { + className: "compatibility-browser-version", + }, + version + ) + ) + ); + } +} + +module.exports = UnsupportedBrowserItem; diff --git a/devtools/client/inspector/compatibility/components/UnsupportedBrowserList.js b/devtools/client/inspector/compatibility/components/UnsupportedBrowserList.js new file mode 100644 index 0000000000..51b513253f --- /dev/null +++ b/devtools/client/inspector/compatibility/components/UnsupportedBrowserList.js @@ -0,0 +1,76 @@ +/* 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 UnsupportedBrowserItem = createFactory( + require("resource://devtools/client/inspector/compatibility/components/UnsupportedBrowserItem.js") +); + +const Types = require("resource://devtools/client/inspector/compatibility/types.js"); + +class UnsupportedBrowserList extends PureComponent { + static get propTypes() { + return { + browsers: PropTypes.arrayOf(PropTypes.shape(Types.browser)).isRequired, + }; + } + + render() { + const { browsers } = this.props; + + const unsupportedBrowserItems = {}; + + const unsupportedVersionsListByBrowser = new Map(); + + for (const { name, version, status } of browsers) { + if (!unsupportedVersionsListByBrowser.has(name)) { + unsupportedVersionsListByBrowser.set(name, []); + } + unsupportedVersionsListByBrowser.get(name).push({ version, status }); + } + + for (const { id, name, version, status } of browsers) { + // Only display one icon per browser + if (!unsupportedBrowserItems[id]) { + if (status === "esr") { + // The data is ordered by version number, so we'll show the first unsupported + // browser version. This might be confusing for Firefox as we'll show ESR + // version first, and so the user wouldn't be able to tell if there's an issue + // only on ESR, or also on release. + // So only show ESR if there's no newer unsupported version + const newerVersionIsUnsupported = browsers.find( + browser => browser.id == id && browser.status !== status + ); + if (newerVersionIsUnsupported) { + continue; + } + } + + unsupportedBrowserItems[id] = UnsupportedBrowserItem({ + key: id, + id, + name, + version, + unsupportedVersions: unsupportedVersionsListByBrowser.get(name), + }); + } + } + return dom.ul( + { + className: "compatibility-unsupported-browser-list", + }, + Object.values(unsupportedBrowserItems) + ); + } +} + +module.exports = UnsupportedBrowserList; diff --git a/devtools/client/inspector/compatibility/components/moz.build b/devtools/client/inspector/compatibility/components/moz.build new file mode 100644 index 0000000000..b4a1c7cf7c --- /dev/null +++ b/devtools/client/inspector/compatibility/components/moz.build @@ -0,0 +1,20 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +DevToolsModules( + "BrowserIcon.js", + "CompatibilityApp.js", + "Footer.js", + "IssueItem.js", + "IssueList.js", + "IssuePane.js", + "NodeItem.js", + "NodeList.js", + "NodePane.js", + "Settings.js", + "UnsupportedBrowserItem.js", + "UnsupportedBrowserList.js", +) |