AccessibilityTreeFilter.js (5168B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 "use strict"; 5 6 // React 7 const { 8 createFactory, 9 Component, 10 } = require("resource://devtools/client/shared/vendor/react.mjs"); 11 const { 12 div, 13 hr, 14 span, 15 } = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); 16 const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs"); 17 const { 18 L10N, 19 } = require("resource://devtools/client/accessibility/utils/l10n.js"); 20 21 loader.lazyGetter(this, "MenuButton", function () { 22 return createFactory( 23 require("resource://devtools/client/shared/components/menu/MenuButton.js") 24 ); 25 }); 26 loader.lazyGetter(this, "MenuItem", function () { 27 return createFactory( 28 require("resource://devtools/client/shared/components/menu/MenuItem.js") 29 ); 30 }); 31 loader.lazyGetter(this, "MenuList", function () { 32 return createFactory( 33 require("resource://devtools/client/shared/components/menu/MenuList.js") 34 ); 35 }); 36 37 const actions = require("resource://devtools/client/accessibility/actions/audit.js"); 38 39 const { 40 connect, 41 } = require("resource://devtools/client/shared/vendor/react-redux.js"); 42 const { 43 FILTERS, 44 } = require("resource://devtools/client/accessibility/constants.js"); 45 46 const FILTER_LABELS = { 47 [FILTERS.NONE]: "accessibility.filter.none", 48 [FILTERS.ALL]: "accessibility.filter.all2", 49 [FILTERS.CONTRAST]: "accessibility.filter.contrast", 50 [FILTERS.KEYBOARD]: "accessibility.filter.keyboard", 51 [FILTERS.TEXT_LABEL]: "accessibility.filter.textLabel", 52 }; 53 54 class AccessibilityTreeFilter extends Component { 55 static get propTypes() { 56 return { 57 auditing: PropTypes.array.isRequired, 58 filters: PropTypes.object.isRequired, 59 dispatch: PropTypes.func.isRequired, 60 describedby: PropTypes.string, 61 toolboxDoc: PropTypes.object.isRequired, 62 audit: PropTypes.func.isRequired, 63 }; 64 } 65 66 async toggleFilter(filterKey) { 67 const { audit: auditFunc, dispatch, filters } = this.props; 68 69 if (filterKey !== FILTERS.NONE && !filters[filterKey]) { 70 Glean.devtoolsAccessibility.auditActivated[filterKey].add(1); 71 72 dispatch(actions.auditing(filterKey)); 73 await dispatch(actions.audit(auditFunc, filterKey)); 74 } 75 76 // We wait to dispatch filter toggle until the tree is ready to be filtered 77 // right after the audit. This is to make sure that we render an empty tree 78 // (filtered) while the audit is running. 79 dispatch(actions.filterToggle(filterKey)); 80 } 81 82 onClick(filterKey) { 83 this.toggleFilter(filterKey); 84 } 85 86 render() { 87 const { auditing, filters, describedby, toolboxDoc } = this.props; 88 const toolbarLabelID = "accessibility-tree-filters-label"; 89 const filterNoneChecked = !Object.values(filters).includes(true); 90 const items = [ 91 MenuItem({ 92 key: FILTERS.NONE, 93 checked: filterNoneChecked, 94 className: `filter ${FILTERS.NONE}`, 95 label: L10N.getStr(FILTER_LABELS[FILTERS.NONE]), 96 onClick: this.onClick.bind(this, FILTERS.NONE), 97 disabled: !!auditing.length, 98 }), 99 hr({ key: "hr-1" }), 100 ]; 101 102 const { [FILTERS.ALL]: filterAllChecked, ...filtersWithoutAll } = filters; 103 items.push( 104 MenuItem({ 105 key: FILTERS.ALL, 106 checked: filterAllChecked, 107 className: `filter ${FILTERS.ALL}`, 108 label: L10N.getStr(FILTER_LABELS[FILTERS.ALL]), 109 onClick: this.onClick.bind(this, FILTERS.ALL), 110 disabled: !!auditing.length, 111 }), 112 hr({ key: "hr-2" }), 113 Object.entries(filtersWithoutAll).map(([filterKey, active]) => 114 MenuItem({ 115 key: filterKey, 116 checked: active, 117 className: `filter ${filterKey}`, 118 label: L10N.getStr(FILTER_LABELS[filterKey]), 119 onClick: this.onClick.bind(this, filterKey), 120 disabled: !!auditing.length, 121 }) 122 ) 123 ); 124 125 let label; 126 if (filterNoneChecked) { 127 label = L10N.getStr(FILTER_LABELS[FILTERS.NONE]); 128 } else if (filterAllChecked) { 129 label = L10N.getStr(FILTER_LABELS[FILTERS.ALL]); 130 } else { 131 label = Object.keys(filtersWithoutAll) 132 .filter(filterKey => filtersWithoutAll[filterKey]) 133 .map(filterKey => L10N.getStr(FILTER_LABELS[filterKey])) 134 .join(", "); 135 } 136 137 return div( 138 { 139 role: "group", 140 className: "accessibility-tree-filters", 141 "aria-labelledby": toolbarLabelID, 142 "aria-describedby": describedby, 143 }, 144 span( 145 { id: toolbarLabelID, role: "presentation" }, 146 L10N.getStr("accessibility.tree.filters") 147 ), 148 MenuButton( 149 { 150 menuId: "accessibility-tree-filters-menu", 151 toolboxDoc, 152 className: `devtools-button badge toolbar-menu-button filters`, 153 label, 154 }, 155 MenuList({}, items) 156 ) 157 ); 158 } 159 } 160 161 const mapStateToProps = ({ audit: { filters, auditing } }) => { 162 return { filters, auditing }; 163 }; 164 165 // Exports from this module 166 module.exports = connect(mapStateToProps)(AccessibilityTreeFilter);